Function GaN_Services(@Service, @Params)
/***********************************************************************************************************************

    Name        :   GaN_Services

    Description :   Handler program for all RDS services.

    Notes       :   Application errors should be logged using the Error Services module. There are a few methodological
                    assumptions built into way errors are managed which are important to understand in order to properly
                    work with Error Services:

                    - The term 'top' refers to the originating procedure of a call stack and the term 'bottom' refers to
                      the last routine (or the current routine) within a call stack. Within the OpenInsight Debugger
                      this will appear backwards since the originating procedure always appears at the bottom of the
                      list and the current routine appears at the top of the list. We are using this orientation because
                      it is common to refer to the process of calling other procedures as 'drilling down'.

                    - The reason for defining the orientation of the call stack is because Error_Services allows for
                      multiple error conditions to be appended to an original error. In most cases this will happen when
                      a procedure at the bottom of the stack generates an error condition and then returns to its
                      calling procedure. This higher level procedure can optionally add more information relevant to
                      itself. This continues as the call stack 'bubbles' its way back to the top to where the
                      originating procedure is waiting.

                    - Native OpenInsight commands that handle errors (e.g., Set_Status, Set_FSError, Set_EventStatus)
                      preserve their error state until explicitly cleared. This can hinder the normal execution of code
                      since subsequent procedures (usually SSPs) will fail if a pre-existing error condition exists.
                      Our philosophy is that error conditions should automatically be cleared before a new procedure
                      is executed to avoid this problem. However, the nature of Basic+ does not make this easy to
                      automate for any given stored procedure. Therefore, if a stored procedure wants to conform to our
                      philosophy then it should include a call into the 'Clear' service request at the top of the
                      program. Alternatively this can be done through a common insert (see SERVICE_SETUP for example.)

                    - Service modules will use the SERVICE_SETUP insert and therefore automatically clear out any
                      error conditions that were set before.

    Parameters  :
        Service         [in] -- Name of the service being requested
        Param1-10   [in/out] -- Additional request parameter holders
        Response       [out] -- Response to be sent back to the Controller (MCP) or requesting procedure

    Metadata    :

    History     :   (Date, Initials, Notes)
        01/11/19    djs     Original programmer.
        07/23/19    djs     Added check within UpdateDisposition service to see if an NCR has been added to External
                            wafers before marking DISP complete and removing the wafers from the WIP.
                            Added check within UpdateDisposition service to see if the DISP_COMPLETE flag has been
                            marked for that wafer before marking the DISP stage complete and moving the wafer out of
                            the DISP Location queue.
        12/17/19    dpc     Removed G_REACT filter from the GetToolList service to allow R69 and R71 to show in Tool
                            filter dropdown.
        07/21/20    djs     Updated the AutoDisposition service to require retained wafers to be signed (i.e. physically
                            retained by an operator) before the wafer is marked as ready to close.
        08/20/20    djs     Removed unnecessary inserts and declarations. Created Disposition_Services which contains
                            new services for dispositioning production and engineering wafers. The AutoDisposition and
                            UpdateDisposition services within this service module are now deprecated and should no
                            longer be used. Gan_Services is approaching the maximum executable size. As a result
                            GaN_Services may need to be refactored and split into multiple service modules in the
                            future.
														

***********************************************************************************************************************/
#pragma precomp SRP_PreCompiler

$insert APP_INSERTS
$insert SERVICE_SETUP
$insert PROD_SPEC_EQUATES
$insert TOOL_CLASS_EQUATES
$insert REACT_RUN_EQUATES
$insert REACTOR_EQUATES
$insert PRS_STAGE_EQUATES
$insert WO_LOG_EQUATES
$insert WO_MAT_EQUATES
$insert RLIST_EQUATES
$insert TOOL_EQUATES
$insert GAN_ETCH_EQUATES
$insert WO_WFR_EQUATES
$insert TOOL_WFR_EQUATES
$insert LOCATION_EQUATES
$insert WO_MAT_WFR_EQUATES
$insert RUN_STAGE_EQUATES
$insert RUN_STAGE_WFR_EQUATES
$insert GAN_RUN_INFO_REQUESTS_EQUATES
$insert GAN_SCHEDULE_EQUATES
$insert GAN_PARAMS_EQUATES
$insert RETAINED_WAFERS_EQUATES
$insert DISPOSITION_REQUESTS_EQUATES
$insert TOOL_ETCH_EQUATES			 

Declare subroutine  Error_Services, SRP_JSON, End_Window, Database_Services, Btree.Extract, SQL_Services
Declare subroutine  Logging_Services, SRP_Array, GaN_Services, Sleepery, WinYield, Yield, Set_Status, Excel_Services
Declare subroutine  Disposition_Services, Engineering_Services
Declare function    Database_Services, SRP_JSON, Logging_Services, Environment_Services, Error_Services    
Declare function    SQL_Services, SRP_Array, NextKey, DateTime, GaN_Services, Excel_Services, Max, SRP_Math
Declare function    GetTickCount

LogPath     = Environment_Services('GetApplicationRootPath') : '\LogFiles\Thruput'
LogDate     = Oconv(Date(), 'D4/')
LogTime     = Oconv(Time(), 'MTS')
LogFileName = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' ThruPut Log - GaN.csv'
Headers     = 'Logging DTM':@FM:'RDS Key ID':@FM:'GRWfrQty':@FM:'ScrapQty':@FM:'ProdTWQty':@FM:'DummyQty'
objLog      = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, ',', Headers, '', False$, False$)
LoggingDTM  = LogDate : ' ' : LogTime   ; // Logging DTM

LogPath     = Environment_Services('GetApplicationRootPath') : '\LogFiles\GaNWIP'
LogFileName = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' WfrQueueLog.csv'
Headers     = 'Logging DTM':@FM:'UserID':@FM:'WfrID':@FM:'Param Queue':@FM:'Wafer Queue':@FM:'Error Message'
objWIPLog   = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, ',', Headers, '', False$, False$)

LogPath      = Environment_Services('GetApplicationRootPath') : '\LogFiles\GaNWIP'
LogFileName  = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' PocketLog.csv'
Headers      = 'Logging DTM':@FM:'RunID':@FM:'Pockets'
objPocketLog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, ',', Headers, '', False$, False$)

LogPath      = Environment_Services('GetApplicationRootPath') : '\LogFiles\GaNWIP'
LogFileName  = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' UnloadLog.csv'
Headers      = 'Logging DTM':@FM:'RDSNo':@FM:'Notes'
objUnloadLog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, ',', Headers, '', False$, False$)

GoToService else
	Error_Services('Set', Service : ' is not a valid service request within the ' : ServiceModule : ' services module.')
end

Return Response else ''


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Service Parameter Options
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Options BOOLEAN     = True$, False$
Options STAGETYPE   = 'Reactor', 'Metrology', 'Disposition'

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Services
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


//----------------------------------------------------------------------------------------------------------------------
// GetGaNGRProps
//
//
//----------------------------------------------------------------------------------------------------------------------
Service GetGaNGRProps(WOMatKey)
   
    If WOMatKey NE '' then
        ReactorType = Xlate('WO_MAT', WOMatKey, 'REACTOR_TYPE', 'X')               
        // The following NCR and TW counting logic is ran against all RDS, for that WorkOrder, everytime a GaN cassette
        // is FQA signed.
        If (ReactorType _EQC 'GAN') then
            
            GRWfrQty 	= 0
            ScrapQty 	= 0
            ProdTWQty	= 0
            DummyQty    = 0
            EmptyQty    = 0
         
            WONo      = Field(WOMatKey, '*', 1)
            WOStepKey = Xlate('WO_MAT', WOMatKey, 'WO_STEP_KEYS', 'X')
            WOStepRec = Database_Services('ReadDataRow', 'WO_STEP', WOStepKey)
            WOMatKeys = Xlate('WO_LOG', WONo, 'WO_MAT_KEY', 'X')
            CurrDTM   = Datetime()           
            GRWfrQty  = Xlate('WO_MAT_WFR', WOMatKey, 'OUT_WFR_QTY', 'X')
            
            // Scan through each wafer in this work order to count the number of WMI, RDS, and WMO NCRs which have
            // not been reported to SAP yet and report them with this outbound cassette batch ID.
            For each InboundMatKey in WOMatKeys using @VM
                CassNo = Field(InboundMatKey, '*', 2)
                SlotNos = Xlate('WO_MAT', InboundMatKey, 'SLOT_NO', 'X')
                For each SlotNo in SlotNos using @VM
                    WOWfrKey = WONo:'*':CassNo:'*':SlotNo
                    Database_Services('ActivateRecord', 'WO_WFR', WOWfrKey)
                    OrigRec = @Record
                    If ( ({WMI_NCR_NO} NE '') and ({WMI_NCR_SAP_DTM} EQ '') ) then
                        {WMI_NCR_SAP_DTM} = CurrDTM
                        ScrapQty += 1
                    end
                    If ( ({RDS_NCR_NO} NE '') and ({RDS_NCR_SAP_DTM} EQ '') ) then
                        {RDS_NCR_SAP_DTM} = CurrDTM
                        ScrapQty += 1
                    end
                    If ( ({WMO_NCR_NO} NE '') and ({WMO_NCR_SAP_DTM} EQ '') ) then
                        {WMO_NCR_SAP_DTM} = CurrDTM
                        ScrapQty += 1
                    end
                    If OrigRec NE @Record then
                        Database_Services('WriteDataRow', 'WO_WFR', WOWfrKey, @Record, True$, False$, True$)
                    end
                Next SlotNo
            Next InboundMatKey
            
            
            // Scan through all RDS records in this work order to count the number of test wafers which have not
            // yet been reported to SAP.
            RDSKeys = WOStepRec<WO_STEP_RDS_KEY$>
            For each RDSKey in RDSKeys using @VM
                ReactRunRec  = Database_Services('ReadDataRow', 'REACT_RUN', RDSKey)                
                GaNRunID     = ReactRunRec<REACT_RUN_GAN_RUN_ID$>
                Scribes      = Xlate('REACT_RUN', RDSKey, 'WFR_SCRIBES', 'X')                
                DummyQty     = 0
                EmptyQty     = 0

                For each Scribe in Scribes using @VM setting vPos
                    DummyID = GanRunID[1,7]:'D'
                    EmptyID = GaNRunID[1,7]:'E'                
                    EmptyFlag = (Indexc(Scribe, 'EMPTY', 1) > 0)
                    If Scribe[1,8] EQ DummyID then
                        DummyQty += 1
                    end
                    If ( (Scribe[1,8] EQ EmptyID) or (EmptyFlag EQ True$) ) then
                        EmptyQty += 1
                    end
                Next Scribe
                                               
                SigProfile    = ReactRunRec<REACT_RUN_WFR_STAGE$>
                SigProfileCnt = DCount(SigProfile, @VM)
                NeedToWrite   = False$                                                                  
                
                InWfrIDs = ReactRunRec<REACT_RUN_IN_WFR_ID$>
                For each InWfrID in InWfrIDs using @VM setting vPos
                    If InWfrID NE '' then                        
                        // Count the number of TWs which have not been reported to SAP yet
                        For SigProfileIndex = 1 to SigProfileCnt
                            Destroyed = False$
                            Stage = SigProfile<1, SigProfileIndex>
                            WfrKID = InWfrID
                            Convert '*' to '.' in WfrKID                                
                            WfrStageKey = RDSKey:'*':Stage:'*':WfrKID
                                                  
                            StageDestTest	= XLATE('RUN_STAGE_WFR',WfrStageKey,'DEST_TEST','X')
                            StageCompSig	= XLATE('RUN_STAGE_WFR',WfrStageKey,'COMP_BY','X')
                            If ( (StageDestTest EQ True$) AND (StageCompSig NE '') ) then
                                Database_Services('ActivateRecord', 'WO_WFR', InWfrID)
                                If {TW_SAP_DTM} EQ '' then
                                    ProdTWQty  += 1
                                    {TW_SAP_DTM} = CurrDTM
                                    Database_Services('WriteDataRow', 'WO_WFR', InWfrID, @Record, True$, False$, True$) 
                                end
                            end                               
                        until Destroyed	EQ True$                                  
                        next SigProfileIndex
                    end
                Next InWfrID                
           next RDSKey             
        end else
            Error_Services('Add', 'WO_MAT ':WOMatKey:' is not of reactor type GAN.')
        end
    end else
        Error_Services('Add', 'Null WO_MAT key supplied to ':Service:'.')
    end
    
    Response = GRWfrQty:@FM:ScrapQty:@FM:ProdTWQty

end service


//----------------------------------------------------------------------------------------------------------------------
// GetYieldInfo
//
//
//----------------------------------------------------------------------------------------------------------------------
Service GetYieldInfo(RDSNo, WOMatKey)

    Response    = ''
    If RDSNo NE '' then WOMatKey    = Xlate('REACT_RUN', RDSNo, 'WO_MAT_KEY', 'X')      
    If WOMatKey NE '' then
        WONo        = Field(WOMatKey, '*', 1)
        WOMatRec	= Database_Services('ReadDataRow', 'WO_MAT', WOMatKey)
        If Error_Services('NoError') then
            If RDSNo EQ '' then RDSNo = WOMatRec<WO_MAT_RDS_NO$>
            GRWfrQty 	= 0
            ScrapQty 	= 0
            ProdTWQty	= 0
            DummyQty    = 0
            EmptyQty    = 0

            ReactRunRec = Database_Services('ReadDataRow', 'REACT_RUN', RDSNo)
            GaNRunID    = ReactRunRec<REACT_RUN_GAN_RUN_ID$>
            Scribes     = Xlate('REACT_RUN', RDSNo, 'WFR_SCRIBES', 'X')
            GRWfrQty    = DCount(Scribes, @VM)

            For each Scribe in Scribes using @VM setting vPos
                DummyID = GanRunID[1,7]:'D'
                EmptyID = GaNRunID[1,7]:'E'                
                EmptyFlag = (Indexc(Scribe, 'EMPTY', 1) > 0)

                If Scribe[1,8] EQ DummyID then
                    DummyQty += 1
                end
                If ( (Scribe[1,8] EQ EmptyID) or (EmptyFlag EQ True$) ) then
                    EmptyQty += 1
                end
            Next Scribe
            
            GRWfrQty -= EmptyQty

            // The following NCR and TW counting logic is ran against all RDS, for that WorkOrder,
            // everytime a GaN cassette is FQA signed.                       

            SigProfile    = ReactRunRec<REACT_RUN_WFR_STAGE$>
            SigProfileCnt = DCount(SigProfile, @VM)

            // Count the number of NCRs
            NCRKeys = Xlate('REACT_RUN', RDSNo, 'NCR_NOS', 'X')
            If (NCRKeys NE '') then
                ScrapQty += SUM(XLATE('NCR', NCRKeys, 'REJ_CNT', 'X'))
            end

            // Count the number of Test Wafers
            InWfrIDs = ReactRunRec<REACT_RUN_IN_WFR_ID$>
*            If WONo EQ 169733 then
                ProdTWQty = Sum(ReactRunRec<REACT_RUN_TEST_WAFER$>)
*            end else
*                For each InWfrID in InWfrIDs using @VM setting vPos
*                    If InWfrID NE '' then
*                        Scribe = Scribes<0, vPos>
*                        ScrapID = GaNRunID[1,7]:'W'
*                        If (Scribe[1, 8] EQ ScrapID) then
*                            ProdTWQty += 1
*                        end else
*                            For SigProfileIndex = 1 to SigProfileCnt
*                                Destroyed = False$
*                                Stage = SigProfile<1, SigProfileIndex>
*                                WfrKID = InWfrID
*                                Convert '*' to '.' in WfrKID                                
*                                WfrStageKey = RDSNo:'*':Stage:'*':WfrKID
*                                                      
*                                StageDestTest	= XLATE('RUN_STAGE_WFR',WfrStageKey,'DEST_TEST','X')
*                                StageCompSig	= XLATE('RUN_STAGE_WFR',WfrStageKey,'COMP_BY','X')
*                                StageStatus     = Xlate('RUN_STAGE_WFR',WfrStageKey,'STATUS','X')
*                                StageSkipped    = StageStatus EQ 'SKIP'
*                                If ( (StageDestTest EQ True$) AND (StageCompSig NE '') and (StageSkipped EQ False$) ) then
*                                    ProdTWQty  += 1
*                                    Destroyed  = True$
*                                end                               
*                            until Destroyed	EQ True$                                  
*                            next SigProfileIndex
*                        end
*                    end
*                Next InWfrID
*            end
* 
*            LogData     = ''
*            LogData<1>  = LoggingDTM    
*            LogData<2>  = RDSNo                
*            LogData<3>  = GRWfrQty                 
*            LogData<4>  = ScrapQty
*            LogData<5>  = ProdTWQty
*            LogData<6>  = DummyQty       
*            Logging_Services('AppendLog', objLog, LogData, @RM, @FM)
            Response = GRWfrQty:@FM:ScrapQty:@FM:ProdTWQty:@FM:DummyQty
        end else
            Error_Services('Add', 'Error reading WO_MAT record in ':Service:'.')
        end
    end else
        Error_Services('Add', 'Error reading WO_MAT_KEY from REACT_RUN table in ':Service:'.')
    end
        
end service


//----------------------------------------------------------------------------------------------------------------------
// GetRunInfo
//
// Input:
//      GaNRunID - [Required]
//
// Output:
//      Returns the following information from the Wafer Track database:
//          1 - GaN Run/Etch ID Recipe
//          2 - GaN Susc Serial Number
//          3 - Pocket Numbers
//          4 - Wafer Scribes
//          5 - Sattelite Serial Numbers
//          6 - Part Number
//          7 - Reactor Number
// Output Format:
//      Rows are @FM delimited, Columns are @VM delimited
//
// Note:
//      This service cannot run directly on the App server due to the fact that the Wafer Track database
//      is within the EC domain. 
//
//----------------------------------------------------------------------------------------------------------------------
Service GetRunInfo(GaNRunID)

    If GaNRunID NE '' then
        Query = 'Declare @RunNumber   varchar(50) ' |
              : "Set @RunNumber     = '":GaNRunID:"' " |
              : 'SELECT ' |
              :     '[Recipe] ' |
              :     ',[Platter S/N] ' |
              :     ',[Pocket_Number] ' |
              :     ',[Wafer_Lot] ' |
              :     ',[Satellite S/N] ' |
              :     ',[Part Number] ' |
              : 'FROM [G4Wafers_01].[dbo].[Prerun Info] ' |
              : 'WHERE [Run Number] =  @RunNumber ' |
              : 'ORDER BY [Pocket_Number]'
        
        Done           = False$
        MaxNumAttempts = 5
        LoopIndex      = 0
        Loop
            LoopIndex  += 1 
            Response    = ''
            GaNRunData  = SQL_Services('PostSQLRequest', 'IQSDMS1', Query)
            If Error_Services('NoError') then
                Convert @FM to @VM in GaNRunData
                Convert @RM to @FM in GaNRunData
                NumRows = DCount(GaNRunData, @FM)
                For RowIndex = 1 to NumRows
                    GaNRunData<RowIndex, 7> = GaNRunID[1, 2]
                Next RowIndex
                GaNRunData = SRP_Array('Rotate', GaNRunData, @FM, @VM)
                Response   = GaNRunData
                PocketNos  = Response<3>
                NumWfrs    = DCount(PocketNos, @VM)
                If NumWfrs GT 4 then Done = True$
                If ( (PocketNos EQ 0) or (PocketNos EQ '') ) then
                    // Log error and hardcode the pockets
                    LogData    = ''
                    LogData<1> = LoggingDTM
                    LogData<2> = GaNRunID
                    Convert @VM to ' ' in PocketNos
                    LogData<3> = PocketNos
                    Logging_Services('AppendLog', objPocketLog, LogData, @RM, @FM)
                    Scribes = Response<4>
                    For each Scribe in Scribes using @VM setting vPos
                        PocketNos<0, vPos> = Fmt(vPos,"R(0)#2")
                    Next Scribe
                    Response<3> = PocketNos
                end
            end else
                Done     = True$
                Response = Error_Services('GetMessage')
            end
        Until ( (Done EQ True$) or (LoopIndex EQ MaxNumAttempts) )
        Repeat
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// GetToolList
//
// Output:
//      Returns a @FM delimited array of GaN related tools.
//
//----------------------------------------------------------------------------------------------------------------------
Service GetToolList(ReactorFlag)
 
    Response = Database_Services('ReadDataRow', 'APP_INFO', 'GAN_TOOL_LIST')

end service


//----------------------------------------------------------------------------------------------------------------------
// GetAvailableRunIDs
//
// Output:
//      Returns a @FM delimited list of GaN Run IDs, which have not yet been bound to an RDS.
//
//----------------------------------------------------------------------------------------------------------------------
Service GetAvailableRunIDs(Reactor, WorkOrder)

    Response  = ''
    AllRunIDs =  Database_Services('ReadDataRow', 'APP_INFO', 'PRERUN_GAN_RUN_IDS')
    For each RunID in AllRunIDs using @FM setting fPos
        Convert @Lower.Case to @Upper.Case in RunID
        If RunID[1, 2] EQ Reactor then
            If WorkOrder NE '' then
                GaNSchedWorkOrder = Xlate('GAN_SCHEDULE', RunID, 'WORK_ORDER', 'X')                            
                If WorkOrder EQ GaNSchedWorkOrder then
                    Convert @Upper.Case to @Lower.Case in RunID
                    Response<-1> = RunID
                end
            end else
                Convert @Upper.Case to @Lower.Case in RunID            
                Response<-1> = RunID
            end
        end
    Next RunID
    
end service


//----------------------------------------------------------------------------------------------------------------------
// GetAvailableEtchIDs
//
// Output:
//      Returns a @FM delimited list of GaN Etch IDs, which have not yet been bound to an RDS.
//
//----------------------------------------------------------------------------------------------------------------------
Service GetAvailableEtchIDs(Reactor)
    
    Response  = ''
    AllEtchIDs =  Database_Services('ReadDataRow', 'APP_INFO', 'PRERUN_GAN_ETCH_IDS')
    For each EtchID in AllEtchIDs using @FM setting fPos
        If EtchID[1, 2] EQ Reactor then
            Response<-1> = EtchID
        end
    Next EtchID
    
end service


//----------------------------------------------------------------------------------------------------------------------
// ConsumeExtEtchID
//
// Input: ExtEtchID [Required]
//
// Output:
//      True$ (1) if successful, False$ (0) otherwise.
//
//----------------------------------------------------------------------------------------------------------------------
Service ConsumeExtEtchID(ExtEtchID)
    
    Reponse  = False$
    ElapTime = 0
    If ExtEtchID NE '' then       
        RequestTime = Time()
        Loop
            ElapTime = Time() - RequestTime
            HaveLock = Database_Services('GetKeyIDLock', 'APP_INFO', 'PRERUN_GAN_ETCH_IDS')
        Until HaveLock or ElapTime GT 5
        Repeat
        
        If HaveLock then
            AvailableEtchIDs = Database_Services('ReadDataRow', 'APP_INFO', 'PRERUN_GAN_ETCH_IDS')
            Locate ExtEtchID in AvailableEtchIDs using @FM setting fPos then
                AvailableEtchIDs = Delete(AvailableEtchIDs, fPos, 0, 0)
                Database_Services('WriteDataRow', 'APP_INFO', 'PRERUN_GAN_ETCH_IDS', AvailableEtchIDs, True$, False$, True$)
                Reponse = True$
                ConsumedEtchIDs = Database_Services('ReadDataRow', 'APP_INFO', 'PRERUN_ETCH_IDS_CONSUMED')
                // Add this Etch ID to the consumed Etch ID list to use for filtering the combo box on the REACT_RUN
                // form.
                Locate ExtEtchID in ConsumedEtchIDs using @FM setting cPos else
                    ConsumedEtchIDs<-1> = ExtEtchID
                    ConsumedEtchIDs     = SRP_Array('SortRows', ConsumedEtchIDs, 'AR1', 'LIST')
                    Database_Services('WriteDataRow', 'APP_INFO', 'PRERUN_ETCH_IDS_CONSUMED', ConsumedEtchIDs, True$, False$, True$)
                end
            end
            Database_Services('ReleaseKeyIDLock', 'APP_INFO', 'PRERUN_GAN_ETCH_IDS')
        end
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// RemoveRunID
//
// Input: RunID [Required]
//
// Output:
//      True$ (1) if successful, False$ (0) otherwise.
//
//----------------------------------------------------------------------------------------------------------------------
Service RemoveRunID(RunID)
    
    Reponse = False$
    If RunID NE '' then
        RequestTime = Time()
        Loop
            ElapTime = Time() - RequestTime
            HaveLock = Database_Services('GetKeyIDLock', 'APP_INFO', 'PRERUN_GAN_RUN_IDS', True$)
        Until HaveLock or ElapTime GT 5
        Repeat
        
        If HaveLock then
            AvailableRunIDs = Database_Services('ReadDataRow', 'APP_INFO', 'PRERUN_GAN_RUN_IDS')
            Locate RunID in AvailableRunIDs using @FM setting fPos then
                AvailableRunIDs = Delete(AvailableRunIDs, fPos, 0, 0)
                Database_Services('WriteDataRow', 'APP_INFO', 'PRERUN_GAN_RUN_IDS', AvailableRunIDs, True$, False$, False$)
                Response = True$
            end
            Database_Services('ReleaseKeyIDLock', 'APP_INFO', 'PRERUN_GAN_RUN_IDS')
            ConsumedRunIDs = Database_Services('ReadDataRow', 'APP_INFO', 'PRERUN_RUN_IDS_CONSUMED')
            // Add this Run ID to the consumed Run ID list to use for filtering the combo box on the REACT_RUN form.
            Locate RunID in ConsumedRunIDs using @FM setting cPos else
                ConsumedRunIDs<-1> = RunID
                ConsumedRunIDs     = SRP_Array('SortSimpleList', ConsumedRunIDs)
                Database_Services('WriteDataRow', 'APP_INFO', 'PRERUN_RUN_IDS_CONSUMED', ConsumedRunIDs, True$, False$, True$)
            end
        end
        
        If ElapTime GT 5 then
            Error_Services('Add', 'Error removing Run ID ':RunID: ' from PRERUN_GAN_RUN_IDS')
            LogData = ''
            LogData<1> = LoggingDTM
            LogData<2> = @USER4
            LogData<3> = EtchID
            Machine    = Environment_Services('GetServer')
            Logging_Services('AppendLog', ObjLog, LogData, @RM, @FM, False$)       
        end
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// UpdateAvailableRunIDs
//
//
//----------------------------------------------------------------------------------------------------------------------
Service UpdateAvailableRunIDs()

    hSysLists   = Database_Services('GetTableHandle', 'SYSLISTS')
    Lock hSysLists, ServiceKeyID then
        SearchDTM = OConv( (DateTime() - 2) , 'DT')
        Response     = ''
        WaferTrackDB = Environment_Services('GetWaferTrackProductionPath')
        Query = 'Declare @RunDTM   varchar(50) ' |
              : "Set @RunDTM     = '":SearchDTM:"' " |
              : 'SELECT DISTINCT ' |
              :     '[Run Number] ' |
              : 'FROM [G4Wafers_01].[dbo].[Prerun Info] ' |
              : "WHERE [Date] >=  @RunDTM and ([Run Number] like '%mvfet' or [Run Number] like '%hvfet') " |
              : "ORDER BY [Run Number] " 
              
        NewGaNRunIDs  = SQL_Services('GetDataRows', 'IQSDMS1', Query, 10)
        Convert @FM to @VM in NewGaNRunIDs
        Convert @RM to @FM in NewGanRunIDs
        CurrGaNRunIDs  = Database_Services('ReadDataRow', 'APP_INFO', 'PRERUN_GAN_RUN_IDS')
        HaveLock       = Database_Services('GetKeyIDLock', 'APP_INFO', 'PRERUN_GAN_RUN_IDS', True$)
        ConsumedRunIDs = Database_Services('ReadDataRow', 'APP_INFO', 'PRERUN_RUN_IDS_CONSUMED')
        NeedToWrite    = False$
        If HaveLock EQ True$ then
            // OK to update
            If (NewGaNRunIDs NE '') and (NewGaNRunIDs NE 0) then
                // Merge new and current list ensuring unique values   
                For each GaNRunID in NewGaNRunIDs using @FM setting gPos
                    Locate GaNRunID in CurrGaNRunIDs using @FM setting fPos else
                        // Check if etch ID has already been consumed. Only add the etch ID to the available list if
                        // it has not yet been consumed.
                        Locate GaNRunID in ConsumedRunIDs using @FM setting cPos else
                            CurrGaNRunIDs<-1> = GaNRunID
                        end                        
                    end
                Next GaNRunID
                // Sort list to ensure list is sorted
                CurrGaNRunIDs = SRP_Array('SortRows', CurrGanRunIDs, 'AL1', 'LIST', @FM, '')
                Database_Services('WriteDataRow', 'APP_INFO', 'PRERUN_GAN_RUN_IDS', CurrGaNRunIDs, True$, False$, True$)
            end
            Database_Services('ReleaseKeyIDLock', 'APP_INFO', 'PRERUN_GAN_RUN_IDS')
        end
        Unlock hSysLists, ServiceKeyID else Null
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// GetReactor
//
//
//----------------------------------------------------------------------------------------------------------------------
Service GetReactor(WorkOrderNo)

    Reactor = ''
    If (WorkOrderNo NE '') then
        If RowExists('WO_LOG', WorkOrderNo) then          
            PVDesc = Xlate('WO_LOG', WorkOrderNo, 'PROD_VER_DESC', 'X')
            Begin Case
                Case Indexc(PVDesc, 'G5+', 1)
                    Reactor = 69
                Case Indexc(PVDesc, 'G5', 1)
                    Reactor = 71
                Case Otherwise$
                    Error_Services('Add', 'Error in ':Service:'. PROD_VER_DESC does not match any GaN reactors.')
            End Case
        end else
            Error_Services('Add', 'Error in ':Service:'. WO_LOG record ':WorkOrderNo:' does not exist.')
        end
    end
    Response = Reactor
    
end service


//----------------------------------------------------------------------------------------------------------------------
// UpdateAvailableEtchIDs
//
//
//----------------------------------------------------------------------------------------------------------------------
Service UpdateAvailableEtchIDs()
    
    hSysLists   = Database_Services('GetTableHandle', 'SYSLISTS')
    Lock hSysLists, ServiceKeyID then      
        SearchDTM = OConv( (DateTime() - 1) , 'DT')
        Response     = ''
        WaferTrackDB = Environment_Services('GetWaferTrackProductionPath')
        Query = 'Declare @RunDTM   varchar(50) ' |
              : "Set @RunDTM     = '":SearchDTM:"' " |
              : 'SELECT DISTINCT ' |
              :     '[Run Number] ' |
              : 'FROM [G4Wafers_01].[dbo].[Prerun Info] ' |
              : "WHERE [Date] >  @RunDTM and [Run Number] like '%etch' " |
              : "ORDER BY [Run Number] " 
              
        NewGaNEtchIDs   = SQL_Services('GetDataRows', 'IQSDMS1', Query)
        Convert @FM to @VM in NewGaNEtchIDs
        Convert @RM to @FM in NewGanEtchIDs
        CurrGaNEtchIDs  = Database_Services('ReadDataRow', 'APP_INFO', 'PRERUN_GAN_ETCH_IDS')
        HaveLock        = Database_Services('GetKeyIDLock', 'APP_INFO', 'PRERUN_GAN_ETCH_IDS', True$)
        ConsumedEtchIDs = Database_Services('ReadDataRow', 'APP_INFO', 'PRERUN_ETCH_IDS_CONSUMED')
        NeedToWrite     = False$
        If HaveLock EQ True$ then
            // OK to update
            If (NewGaNEtchIDs NE '') and (NewGaNEtchIDs NE 0) then
                // Merge new and current list ensuring unique values
                For each GaNEtchID in NewGaNEtchIDs using @FM setting gPos
                    Locate GaNEtchID in CurrGaNEtchIDs using @FM setting fPos else
                        // Check if etch ID has already been consumed. Only add the etch ID to the available list if
                        // it has not yet been consumed.
                        Locate GaNEtchID in ConsumedEtchIDs using @FM setting cPos else
                            CurrGaNEtchIDs<-1> = GaNEtchID
                        end
                    end
                Next GaNEtchID
                // Sort list to ensure list is sorted
                CurrGaNEtchIDs = SRP_Array('SortRows', CurrGanEtchIDs, 'AL1', 'LIST', @FM, '')
                Database_Services('WriteDataRow', 'APP_INFO', 'PRERUN_GAN_ETCH_IDS', CurrGaNEtchIDs, True$, False$, True$)
            end
            Database_Services('ReleaseKeyIDLock', 'APP_INFO', 'PRERUN_GAN_ETCH_IDS')
        end
        Unlock hSysLists, ServiceKeyID else Null
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// UpdateGaNScheduleInfo
//
//
//----------------------------------------------------------------------------------------------------------------------
Service UpdateGaNScheduleInfo()

    hSysLists   = Database_Services('GetTableHandle', 'SYSLISTS')
    Lock hSysLists, ServiceKeyID then
        GanSchedRec = ''     
        GaNReactors = ''
        Query       = "SELECT REACTOR WITH REACT_TYPE EQ 'GAN'"
        GoSub ClearCursors
        Rlist(Query, Target_ActiveList$, '', '', '')
        EOF  = False$
        If @RecCount then
            Loop
                Until EOF EQ True$
                ReadNext ReactorID else EOF = True$
                Locate ReactorID in GaNReactors using @FM setting rPos else
                    GaNReactors<-1> = ReactorID
                end       
            Repeat
        end
        GoSub ClearCursors
        SchedPath = Environment_Services('GetGaNSchedulePath')
        // GaN Reactor Scheduler Excel Spreadsheet ---------------------
        For each Reactor in GaNReactors using @FM setting fPos
            SchedFilename    = 'R':Reactor:' Growth Schedule.xls'            
            SchedFilePath    = SchedPath:'\':SchedFilename
            SchedExcelHandle = Excel_Services('OpenDocument', SchedFilePath)
            If Error_Services('NoError') then
                SchedWorksheet   = 'Schedule'
                NumExcelRows = Excel_Services('GetNumRows', SchedExcelHandle, SchedWorksheet)
                For Row = (NumExcelRows - 500) to NumExcelRows
                    ExcelRunID = Excel_Services('GetCellValue', SchedExcelHandle, SchedWorksheet, 'E', Row)
                    ValidRunID = (ExcelRunID[1,2] EQ Reactor)
                    If ValidRunID EQ True$ then
                        GaNSchedKey = ExcelRunID
                        // WT = WaferTrack
                        ExcelWTPart     = Excel_Services('GetCellValue', SchedExcelHandle, SchedWorksheet, 'H', Row)
                        ExcelOIPart     = Excel_Services('GetCellValue', SchedExcelHandle, SchedWorksheet, 'I', Row)
                        ExcelRecipe     = Excel_Services('GetCellValue', SchedExcelHandle, SchedWorksheet, 'L', Row)
                        ExcelWorkOrder  = Excel_Services('GetCellValue', SchedExcelHandle, SchedWorkSheet, 'G', Row)
                        If RowExists('GAN_SCHEDULE', GaNSchedKey) then
                            // Row exists, check if recipe has been changed.
                            GaNSchedRec  = Database_Services('ReadDataRow', 'GAN_SCHEDULE', GanSchedKey)
                            GaNWTPart    = GanSchedRec<GAN_SCHEDULE_WAFER_TRACK_PART$>
                            GaNOIPart    = GanSchedRec<GAN_SCHEDULE_OPENINSIGHT_PART$>
                            GaNRecipe    = GanSchedRec<GAN_SCHEDULE_RECIPE$>
                            GaNWorkOrder = GanSchedRec<GAN_SCHEDULE_WORK_ORDER$>
                            If (GaNWTPart NE ExcelWTPart) or (GaNOIPart NE ExcelOIPart) or (GaNRecipe NE ExcelRecipe) or (GaNWorkOrder NE ExcelWorkOrder) then
                                // GaN Schedule updated, update OpenInsight
                                GanSchedRec<GAN_SCHEDULE_WAFER_TRACK_PART$> = ExcelWTPart
                                GanSchedRec<GAN_SCHEDULE_OPENINSIGHT_PART$> = ExcelOIPart
                                GaNSchedRec<GAN_SCHEDULE_RECIPE$>           = ExcelRecipe
                                GaNSchedRec<GAN_SCHEDULE_WORK_ORDER$>       = ExcelWorkOrder
                                Database_Services('WriteDataRow','GAN_SCHEDULE',GaNSchedKey,GanSchedRec,True$,False$,True$)
                            end
                        end else
                            // Row does not yet exist, so create it.                            
                            GanSchedRec<GAN_SCHEDULE_RECIPE$>           = ExcelRecipe
                            GanSchedRec<GAN_SCHEDULE_WAFER_TRACK_PART$> = ExcelWTPart
                            GanSchedRec<GAN_SCHEDULE_OPENINSIGHT_PART$> = ExcelOIPart
                            GanSchedRec<GAN_SCHEDULE_WORK_ORDER$>       = ExcelWorkOrder
                            Database_Services('WriteDataRow','GAN_SCHEDULE',GaNSchedKey,GanSchedRec,True$,False$,True$)
                        end
                    end                                     
                Next Row
                Excel_Services('CloseDocument', SchedExcelHandle)              
            end                
        Next Reactor
        
        Unlock hSysLists, ServiceKeyID else Null
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// UpdateDisposition
//
// This service contains automatic disposition logic for engineering lots.
// 
//----------------------------------------------------------------------------------------------------------------------
Service UpdateDisposition(RDSNo)

    CurrDTM         = DateTime()
    ReactRunRec     = Database_Services('ReadDataRow', 'REACT_RUN', RDSNo)
    OrigReactRunRec = ReactRunRec
    InWfrIDs        = ReactRunRec<REACT_RUN_IN_WFR_ID$>
    ShipFlags       = ReactRunRec<REACT_RUN_CARR_WFR_SHIP$>
    RetainFlags     = ReactRunRec<REACT_RUN_GAN_RET_WFR$>
    InternalFlags   = ReactRunRec<REACT_RUN_INTERNAL$>
    ExternalFlags   = ReactRunRec<REACT_RUN_EXTERNAL$>
    CassDispComp    = ReactRunRec<REACT_RUN_DISP_COMPLETE$>
    WfrsDispReady   = ReactRunRec<REACT_RUN_WFRS_DISP_READY$>
    NCRReqFlags     = ReactRunRec<REACT_RUN_NCR_REQ$>
    CharFlags       = ReactRunRec<REACT_RUN_IN_WFR_CHAR$>
    NumWfrs         = DCount(InWfrIDs, @VM)
    For WfrIndex = 1 to NumWfrs
        WfrID            = InWfrIDs<0, WfrIndex>
        WOWfrRec         = Database_Services('ReadDataRow', 'WO_WFR', WfrID)
        OrigWOWfrRec     = WOWfrRec
        ShipFlag         = ShipFlags<0, WfrIndex>
        RetainFlag       = RetainFlags<0, WfrIndex>
        InternalFlag     = InternalFlags<0, WfrIndex>
        ExternalFlag     = ExternalFlags<0, WfrIndex>
        CharFlag         = CharFlags<0, WfrIndex>
        DispReady        = WfrsDispReady<0, WfrIndex>
        NCRNo            = WOWfrRec<WO_WFR_RDS_NCR_NO$>
        NCRStatus        = Xlate('NCR', NCRNo, 'STATUS', 'X')
        CriticalFailFlag = WOWfrRec<WO_WFR_CRITICAL_FAILURE$>
        NCRReq           = NCRReqFlags<0, WfrIndex>
        DestStatus       = GaN_Services('GetDestroyedStatus', WfrID)
        If ( (CharFlag EQ 'DUMMY') or (CharFlag EQ 'EMPTY') ) then
            DispReady                               = True$
            WfrsDispReady<0, WfrIndex>              = DispReady
            ReactRunRec<REACT_RUN_WFRS_DISP_READY$> = WfrsDispReady
            WOWfrRec<WO_WFR_STATUS$>                = 'Ready to close'
        end
        If DestStatus EQ True$ then
            // Mark wafer's internal flag. Clear all other flags.
            If ( (InternalFlag EQ False$) or (InternalFlag EQ '') ) then                
                ShipFlag                                = False$
                RetainFlag                              = False$
                InternalFlag                            = True$
                ExternalFlag                            = False$
                ShipFlags<0, WfrIndex>                  = ShipFlag
                RetainFlags<0, WfrIndex>                = RetainFlag 
                InternalFlags<0, WfrIndex>              = InternalFlag
                ExternalFlags<0, WfrIndex>              = ExternalFlag
                ReactRunRec<REACT_RUN_CARR_WFR_SHIP$>   = ShipFlags
                ReactRunRec<REACT_RUN_GAN_RET_WFR$>     = RetainFlags 
                ReactRunRec<REACT_RUN_INTERNAL$>        = InternalFlags
                ReactRunRec<REACT_RUN_EXTERNAL$>        = ExternalFlags
            end
        end
       
        MetStatus  = GaN_Services('GetMetrologyStatus', WfrID)
        DispStatus = GaN_Services('GetDispStatus', WfrID)
        
        Begin Case
            Case CriticalFailFlag EQ True$
                // Bow and/or warp critical limit exceeded, so the wafer must be
                // destroyed to prevent damage to candela tools                
                Reason   = 'Bow and/or warp critical limit exceeded'
                Begin Case
                    Case ( (NCRNo NE '') and (NCRStatus EQ 'C') )
                        WOWfrRec<WO_WFR_STATUS$> = 'Ready to close'
                    Case ( (NCRNo NE '') and (NCRStatus NE 'C') )
                        WOWfrRec<WO_WFR_STATUS$> = 'NCR must be closed'
                    Case NCRNo EQ ''
                        WOWfrRec<WO_WFR_STATUS$> = 'Wafer requires an NCR'
                End Case
                DispReady                               = ( (NCRNo NE '') and (NCRStatus EQ 'C') ) 
                WfrsDispReady<0, WfrIndex>              = DispReady
                ReactRunRec<REACT_RUN_WFRS_DISP_READY$> = WfrsDispReady
                
            Case ( (MetStatus EQ True$) or (DispStatus EQ True$) )
                // Check Ship, Interal, External, and Retain conditions to determine if wafer should be removed from the WIP
                FormattedWfrID = WfrID
                Convert '*' to '.' in FormattedWfrID
                Begin Case
                    Case ShipFlag EQ True$
                        GPackQ = GaN_Services('GetLocQueueID', 'G_PACK')
                        ShipID = ReactRunRec<REACT_RUN_OUT_SLOT_ID$, WfrIndex>
                        RunStageWfrKey = RDSNo:'*DISP*':FormattedWfrID
                        RunStageWfrRec = Database_Services('ReadDataRow', 'RUN_STAGE_WFR', RunStageWfrKey)
                        StageStatus    = RunStageWfrRec<RUN_STAGE_WFR_STATUS$>
                        If StageStatus _EQC 'INIT' then
                            // Mark DISP stage as complete
                            RunStageWfrRec<RUN_STAGE_WFR_COMP_BY$>  = @USER4
                            RunStageWfrRec<RUN_STAGE_WFR_COMP_DTM$> = CurrDTM
                            RunStageWfrRec<RUN_STAGE_WFR_STATUS$>   = 'COMP'
                            Database_Services('WriteDataRow', 'RUN_STAGE_WFR', RunStageWfrKey, RunStageWfrRec, True$, False$, True$)
                        end
                        Begin Case
                            Case (ShipID EQ '')
                                LocQ                     = GaN_Services('GetWfrQueue', WfrID)
                                WOWfrRec<WO_WFR_STATUS$> = 'Wafer needs to be placed in an outbound cassette'
                                If LocQ _EQC GPackQ then
                                    // Wafer is being removed from an outbound cassette, so place the wafer back into the G_PACK
                                    // queue and reset the G_PACK RUN_STAGE_WFR record to 'INIT'.
                                    RunStageWfrKey              = RDSNo:'*G_PACK*':FormattedWfrID
                                    RunStageWfrRec              = Database_Services('ReadDataRow', 'RUN_STAGE_WFR', RunStageWfrKey)
                                    StageStatus                 = RunStageWfrRec<RUN_STAGE_WFR_STATUS$>                                
                                    If StageStatus _EQC 'COMP' then
                                        RunStageWfrRec<RUN_STAGE_WFR_COMP_BY$>  = ''
                                        RunStageWfrRec<RUN_STAGE_WFR_COMP_DTM$> = ''
                                        RunStageWfrRec<RUN_STAGE_WFR_STATUS$>   = 'INIT'
                                        Database_Services('WriteDataRow', 'RUN_STAGE_WFR', RunStageWfrKey, RunStageWfrRec, True$, False$, True$)
                                        Gan_Services('MoveWfrToQueue', WfrID, GPackQ)
                                    end
                                end else
                                    // Wafer has been marked as shipped in the Disposition Report (Excel spreadsheet), but has
                                    // not yet been added to an outbound cassette. Remove the wafer from its current location
                                    // (which likely is DISP), then place the wafer in the G_PACK location queue and update
                                    // the wafer trace (WO_WFR record).
                                    Gan_Services('RemoveWfrFromWIP', WfrID)
                                    Gan_Services('MoveWfrToQueue', WfrID, GPackQ)
                                    // Update wafer trace (WO_WFR record)
                                    LocDTM                               = WOWfrRec<WO_WFR_LOC_DTM$>
                                    NextPos                              = DCount(LocDTM, @VM) + 1
                                    WOWfrRec<WO_WFR_LOC_DTM$, NextPos>   = CurrDTM
                                    WOWfrRec<WO_WFR_LOC_BY$, NextPos>    = @USER4
                                    WOWfrRec<WO_WFR_LOC_EVENT$, NextPos> = 'COMP'
                                    WOWfrRec<WO_WFR_TOOL_ID$, NextPos>   = ''
                                    WOWfrRec<WO_WFR_INV_LOC$, NextPos>   = GPackQ
                                end                           
                            Case ( (ShipID NE '') and ( (CassDispComp EQ False$) or (CassDispComp EQ '') ) )
                                // Wafer is ready for disposition, so mark it as such. Once user marks the cassette
                                // disposition complete, then the wafer will move off the WIP. Move wafer to the G_PACK
                                // location queue if it is not there and update the wafer trace (WO_WFR record).
                                If (NCRReq EQ False$) or ( (NCRReq EQ True$) and (NCRNo NE '') ) then
                                    LocQ = Gan_Services('GetWfrQueue', WfrID)
                                    Gan_Services('RemoveWfrFromWIP', WfrID)
                                    Gan_Services('MoveWfrToQueue', WfrID, GPackQ)
                                    // Update wafer trace (WO_WFR record)
                                    LocDTM   = WOWfrRec<WO_WFR_LOC_DTM$>
                                    NextPos  = DCount(LocDTM, @VM) + 1
                                    WOWfrRec<WO_WFR_LOC_DTM$, NextPos>   = CurrDTM
                                    WOWfrRec<WO_WFR_LOC_BY$, NextPos>    = @USER4
                                    WOWfrRec<WO_WFR_LOC_EVENT$, NextPos> = 'G_PACK'
                                    WOWfrRec<WO_WFR_TOOL_ID$, NextPos>   = ''
                                    WOWfrRec<WO_WFR_INV_LOC$, NextPos>   = GPackQ
                                    WOWfrRec<WO_WFR_STATUS$>             = 'Ready to close'
                                    DispReady = True$
                                    WfrsDispReady<0, WfrIndex> = DispReady
                                    ReactRunRec<REACT_RUN_WFRS_DISP_READY$> = WfrsDispReady
                                end                                                    
                            Case ( (ShipID NE '') and (CassDispComp EQ True$) )
                                // A ship ID has been added (i.e. the wafer has been placed in an outbound cassette and the
                                // user has marked the cassette disposition as complete, so remove the wafer from the 
                                // GaN WIP and mark the G_PACK stage as complete.)
                                RunStageWfrKey           = RDSNo:'*G_PACK*':FormattedWfrID
                                RunStageWfrRec           = Database_Services('ReadDataRow', 'RUN_STAGE_WFR', RunStageWfrKey)
                                StageStatus              = RunStageWfrRec<RUN_STAGE_WFR_STATUS$>
                                LocQ                     = Gan_Services('GetWfrQueue', WfrID)
                                WOWfrRec<WO_WFR_STATUS$> = 'Run closed'
                                Gan_Services('RemoveWfrFromWIP', WfrID)
                                If StageStatus _EQC 'INIT' then                                
                                    // Mark G_PACK stage as complete
                                    RunStageWfrRec<RUN_STAGE_WFR_COMP_BY$>  = @USER4
                                    RunStageWfrRec<RUN_STAGE_WFR_COMP_DTM$> = CurrDTM
                                    RunStageWfrRec<RUN_STAGE_WFR_STATUS$>   = 'COMP'
                                    Database_Services('WriteDataRow', 'RUN_STAGE_WFR', RunStageWfrKey, RunStageWfrRec, True$, False$, True$)
                                end                           
                            Case Otherwise$
                                // No action necessary at this time.
                                Null
                        End Case

                    Case RetainFlag EQ True$
                                                          
                        RetainSlot      = WOWfrRec<WO_WFR_RETAIN_SLOT$>
                        RetainBox       = WOWfrRec<WO_WFR_RETAIN_BOX$>
                        RetainSig       = WOWfrRec<WO_WFR_RETAIN_SIG$>
                        RetainQ         = GaN_Services('GetLocQueueID', 'RETAIN')
                        RunStageWfrKey  = RDSNo:'*DISP*':FormattedWfrID
                        RunStageWfrRec  = Database_Services('ReadDataRow', 'RUN_STAGE_WFR', RunStageWfrKey)
                        StageStatus     = RunStageWfrRec<RUN_STAGE_WFR_STATUS$>
                        If StageStatus _EQC 'INIT' then
                            // Retain flag has been set and metrology is complete, so mark DISP stage as complete.
                            RunStageWfrRec<RUN_STAGE_WFR_COMP_BY$>  = @User4
                            RunStageWfrRec<RUN_STAGE_WFR_COMP_DTM$> = CurrDTM
                            RunStageWfrRec<RUN_STAGE_WFR_STATUS$>   = 'COMP'
                            Database_Services('WriteDataRow', 'RUN_STAGE_WFR', RunStageWfrKey, RunStageWfrRec, True$, False$, True$)
                        end
                        Begin Case
                            
                            Case ( (RetainSlot EQ '') or (RetainBox EQ '') )
                                // Wafer needs to go into RETAIN queue until it receives a Retain Slot and Retain Box 
                                // If wafer is not at RETAIN, then update wafer trace (WO_WFR record) and move the wafer.
                                WOWfrRec<WO_WFR_STATUS$> = 'Wafer requires a retain box and retain slot'     
                                CurrStage                = GaN_Services('GetCurrStage', WfrID)
                                LocQ                     = GaN_Services('GetWfrQueue', WfrID)
                                Gan_Services('RemoveWfrFromWIP', WfrID)
                                Gan_Services('MoveWfrToQueue', WfrID, RetainQ)                            
                                If CurrStage NE 'RETAIN' then                                                       
                                    LocDTM                               = WOWfrRec<WO_WFR_LOC_DTM$>
                                    NextPos                              = DCount(LocDTM, @VM) + 1
                                    WOWfrRec<WO_WFR_LOC_DTM$, NextPos>   = CurrDTM
                                    WOWfrRec<WO_WFR_LOC_BY$, NextPos>    = @USER4
                                    WOWfrRec<WO_WFR_LOC_EVENT$, NextPos> = 'COMP'
                                    WOWfrRec<WO_WFR_TOOL_ID$, NextPos>   = ''
                                    WOWfrRec<WO_WFR_INV_LOC$, NextPos>   = RetainQ
                                end                               
                            Case ( (RetainSlot NE '') and (RetainBox NE '') and (CassDispComp NE True$) )
                                // Wafer is ready for disposition, so mark it as such. Once user marks the cassette
                                // disposition complete, then the wafer will move off the WIP. Move wafer into RETAIN stage
                                // if it is not currently there. This can occur if an RDS is opened after being closed.
                                If ( ( (NCRReq EQ False$) or ( (NCRReq EQ True$) and (NCRNo NE '') ) ) and (RetainSig NE '') ) then
                                    WOWfrRec<WO_WFR_STATUS$> = 'Ready to close'
                                    DispReady                = True$                                    
                                end else
                                    WOWfrRec<WO_WFR_STATUS$> = 'Wafer has not yet been retained'
                                    DispReady                = False$
                                end
                                    RetainQ = 'GCH*Q_RETAIN'
                                    LocQ    = Gan_Services('GetWfrQueue', WfrID)
                                    Gan_Services('RemoveWfrFromWIP', WfrID)
                                    Gan_Services('MoveWfrToQueue', WfrID, RetainQ)                             
                                    LocDTM                                  = WOWfrRec<WO_WFR_LOC_DTM$>
                                    NextPos                                 = DCount(LocDTM, @VM) + 1
                                    WOWfrRec<WO_WFR_LOC_DTM$, NextPos>      = CurrDTM
                                    WOWfrRec<WO_WFR_LOC_BY$, NextPos>       = @USER4
                                    WOWfrRec<WO_WFR_LOC_EVENT$, NextPos>    = 'RETAIN'
                                    WOWfrRec<WO_WFR_TOOL_ID$, NextPos>      = ''
                                    WOWfrRec<WO_WFR_INV_LOC$, NextPos>      = RetainQ
                                    WfrsDispReady<0, WfrIndex>              = DispReady
                                    ReactRunRec<REACT_RUN_WFRS_DISP_READY$> = WfrsDispReady
                            Case ( ( (RetainSlot NE '') and (RetainBox NE '') and (RetainSig NE '') ) and (CassDispComp EQ True$) )
                                // Retain slot and retain box are populated and the user has marked disposition complete ->
                                // mark RETAIN stage complete and remove the wafer from the GaN WIP.
                                RunStageWfrKey           = RDSNo:'*RETAIN*':FormattedWfrID
                                RunStageWfrRec           = Database_Services('ReadDataRow', 'RUN_STAGE_WFR', RunStageWfrKey)
                                StageStatus              = RunStageWfrRec<RUN_STAGE_WFR_STATUS$>
                                LocQ                     = Gan_Services('GetWfrQueue', WfrID)
                                WOWfrRec<WO_WFR_STATUS$> = 'Run closed'
                                Gan_Services('RemoveWfrFromWIP', WfrID)
                                If StageStatus _EQC 'INIT' then
                                    RunStageWfrRec<RUN_STAGE_WFR_COMP_BY$>  = @User4
                                    RunStageWfrRec<RUN_STAGE_WFR_COMP_DTM$> = CurrDTM
                                    RunStageWfrRec<RUN_STAGE_WFR_STATUS$>   = 'COMP'
                                    Database_Services('WriteDataRow', 'RUN_STAGE_WFR', RunStageWfrKey, RunStageWfrRec, True$, False$, True$)                                
                                end
                            Case Otherwise$
                                // No action necessary at this time.
                                Null
                                
                        End Case
                        
                    Case InternalFlag EQ True$
                        Begin Case
                            Case ( (CassDispComp EQ False$) or (CassDispComp EQ '') )                            
                                // Wafer is ready for disposition, so mark it as such. Once user marks the cassette
                                // disposition complete, then the wafer will move off the WIP.
                                DispReady                               = True$
                                WfrsDispReady<0, WfrIndex>              = DispReady
                                ReactRunRec<REACT_RUN_WFRS_DISP_READY$> = WfrsDispReady
                                WOWfrRec<WO_WFR_STATUS$>                = 'Ready to close'
                            Case CassDispComp EQ True$
                                // Remove the wafer from the GaN WIP and mark DISP stage as complete.         
                                RunStageWfrKey           = RDSNo:'*DISP*':FormattedWfrID
                                RunStageWfrRec           = Database_Services('ReadDataRow', 'RUN_STAGE_WFR', RunStageWfrKey)
                                StageStatus              = RunStageWfrRec<RUN_STAGE_WFR_STATUS$>                        
                                LocQ                     = GaN_Services('GetWfrQueue', WfrID)
                                WOWfrRec<WO_WFR_STATUS$> = 'Run closed'
                                Gan_Services('RemoveWfrFromWIP', WfrID)
                                If StageStatus _EQC 'INIT' then                        
                                    // Mark DISP stage as complete
                                    RunStageWfrRec<RUN_STAGE_WFR_COMP_BY$>  = @User4
                                    RunStageWfrRec<RUN_STAGE_WFR_COMP_DTM$> = CurrDTM
                                    RunStageWfrRec<RUN_STAGE_WFR_STATUS$>   = 'COMP'
                                    Database_Services('WriteDataRow', 'RUN_STAGE_WFR', RunStageWfrKey, RunStageWfrRec, True$, False$, True$)
                                end
                        End Case                    
                        
                    Case ExternalFlag EQ True$
                        
                        Begin Case
                            Case ( (CassDispComp EQ False$) or (CassDispComp EQ '') )
                                // Check to see if an NCR has been added to this wafer before marking this wafer as ready
                                // to be dispositioned.
                                If ( (NCRReq EQ False$) or ( (NCRReq EQ True$) and (NCRNo NE '') ) ) then
                                    // Wafer is ready for disposition, so mark it as such. Once user marks the cassette
                                    // disposition complete, then the wafer will move off the WIP.
                                    DispReady                               = True$
                                    WfrsDispReady<0, WfrIndex>              = DispReady
                                    ReactRunRec<REACT_RUN_WFRS_DISP_READY$> = WfrsDispReady
                                    WOWfrRec<WO_WFR_STATUS$>                = 'Ready to close'
                                end
                            Case CassDispComp EQ True$
                                // Remove the wafer from the GaN WIP and mark DISP stage as complete.
                                RunStageWfrKey           = RDSNo:'*DISP*':FormattedWfrID
                                RunStageWfrRec           = Database_Services('ReadDataRow', 'RUN_STAGE_WFR', RunStageWfrKey)
                                StageStatus              = RunStageWfrRec<RUN_STAGE_WFR_STATUS$>
                                WOWfrRec<WO_WFR_STATUS$> = 'Run closed'                            
                                LocQ                     = Gan_Services('GetWfrQueue', WfrID)
                                Gan_Services('RemoveWfrFromWIP', WfrID)                          
                                If StageStatus _EQC 'INIT' then                        
                                    // Mark DISP stage as complete
                                    RunStageWfrRec<RUN_STAGE_WFR_COMP_BY$>  = @User4
                                    RunStageWfrRec<RUN_STAGE_WFR_COMP_DTM$> = CurrDTM
                                    RunStageWfrRec<RUN_STAGE_WFR_STATUS$>   = 'COMP'
                                    Database_Services('WriteDataRow', 'RUN_STAGE_WFR', RunStageWfrKey, RunStageWfrRec, True$, False$, True$)
                                end
                        End Case
                        
                    Case Otherwise$
                        // All flags false. Lot aborted or disposition report not found.
                        Begin Case
                            Case ( (CassDispComp EQ False$) or (CassDispComp EQ '') )
                                // Move wafer to DISP queue if it is not there
                                DispQ = 'GGR*Q_DISP'
                                CurrQ = Gan_Services('GetWfrQueue', WfrID)
                                If CurrQ _NEC DispQ then
                                    Gan_Services('RemoveWfrFromWIP', WfrID)
                                    Gan_Services('MoveWfrToQueue', WfrID, DispQ)
                                end                                                       
                                // Check to see if an NCR has been added to this wafer before marking this wafer as ready
                                // to be dispositioned.
                                If ( (NCRReq EQ False$) or ( (NCRReq EQ True$) and (NCRNo NE '') ) ) then
                                    // Wafer is ready for disposition, so mark it as such. Once user marks the cassette
                                    // disposition complete, then the wafer will move off the WIP.
                                    DispReady                               = True$
                                    WfrsDispReady<0, WfrIndex>              = DispReady
                                    ReactRunRec<REACT_RUN_WFRS_DISP_READY$> = WfrsDispReady
                                    WOWfrRec<WO_WFR_STATUS$>                = 'Ready to close'
                                end else
                                    If NCRReq EQ True$ then
                                        WOWfrRec<WO_WFR_STATUS$> = 'NCR required'
                                    end else
                                        WOWfrRec<WO_WFR_STATUS$> = 'Disposition report not found'
                                    end
                                end
                            Case CassDispComp EQ True$
                                // Remove the wafer from the GaN WIP and mark DISP stage as complete.
                                RunStageWfrKey           = RDSNo:'*DISP*':FormattedWfrID
                                RunStageWfrRec           = Database_Services('ReadDataRow', 'RUN_STAGE_WFR', RunStageWfrKey)
                                StageStatus              = RunStageWfrRec<RUN_STAGE_WFR_STATUS$>
                                WOWfrRec<WO_WFR_STATUS$> = 'Run closed'                           
                                LocQ                     = GaN_Services('GetWfrQueue', WfrID)
                                Gan_Services('RemoveWfrFromWIP', WfrID)
                                If StageStatus _EQC 'INIT' then                        
                                    // Mark DISP stage as complete
                                    RunStageWfrRec<RUN_STAGE_WFR_COMP_BY$>  = @User4
                                    RunStageWfrRec<RUN_STAGE_WFR_COMP_DTM$> = CurrDTM
                                    RunStageWfrRec<RUN_STAGE_WFR_STATUS$>   = 'COMP'
                                    Database_Services('WriteDataRow', 'RUN_STAGE_WFR', RunStageWfrKey, RunStageWfrRec, True$, False$, True$)
                                end
                        End Case                                 
                End Case
                
            Case Otherwise$
                // Metrology or Disposition not complete
                WOWfrRec<WO_WFR_STATUS$> = 'Metrology incomplete'
                
        End Case

        If OrigWOWfrRec NE WOWfrRec then Database_Services('WriteDataRow', 'WO_WFR', WfrID, WOWfrRec, True$, False$, True$)        
    
    Next WfrIndex        
    If OrigReactRunRec NE ReactRunRec then Database_Services('WriteDataRow', 'REACT_RUN', RDSNo, ReactRunRec, True$, False$, True$)

end service


//----------------------------------------------------------------------------------------------------------------------
// GetStageDesc
//
// Input:
//      StageID - [Required]
//
// Output:
//      Returns the GaN stage description associated with the StageID passed in.
//
//----------------------------------------------------------------------------------------------------------------------
Service GetStageDesc(StageID)
    
    Response     = ''
    Done         = False$
    GaNStageInfo = Database_Services('ReadDataRow', 'APP_INFO', 'GAN_STAGES')
    For each StageRow in GanStageInfo using @FM setting fPos
        Locate StageID in StageRow using @VM setting vPos then
            StageDesc = StageRow<0, 2>
            Response  = StageDesc
            Done      = True$
        end
    Until Done EQ True$
    Next StageRow
    
end service


//----------------------------------------------------------------------------------------------------------------------
// GetStageID
//
// Input:
//      StageDesc - [Required]
//
// Output:
//      Returns the GaN stage ID associated with the stage description passed in.
//
//----------------------------------------------------------------------------------------------------------------------
Service GetStageID(StageDesc, StageQ, ToolClass)

    Response     = ''
    If ( (StageDesc NE '') or (StageQ NE '') or (ToolClass NE '') )then
        Done         = False$
        GaNStageInfo = Database_Services('ReadDataRow', 'APP_INFO', 'GAN_STAGES')
        For each StageRow in GanStageInfo using @FM setting fPos
            Begin Case
                Case StageDesc NE ''
                    Locate StageDesc in StageRow using @VM setting vPos then
                        StageID  = StageRow<0, 1>
                        Response = StageID
                        Done     = True$
                    end
                Case StageQ NE ''
                    Locate StageQ in StageRow using @VM setting vPos then
                        StageID  = StageRow<0, 1>
                        Response = StageID
                        Done     = True$
                    end
                Case ToolClass NE ''
                    Locate ToolClass in StageRow using @VM setting vPos then
                        StageID  = StageRow<0, 1>
                        Response = StageID
                        Done     = True$
                    end
            End Case      
        Until Done EQ True$
        Next StageRow
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// GetStageIndex
//
// Input:
//      StageID   - [Required]
//      or
//      StageDesc - [Required]
//
// Output:
//      Returns the GaN stage index associated with the stage ID/description passed in.
//
//----------------------------------------------------------------------------------------------------------------------
Service GetStageIndex(StageID, StageDesc)
    
    Response     = ''
    If (StageID NE '') or (StageDesc NE '') then
        If StageID NE '' then
            StageKey = StageID
        end else
            StageKey = StageDesc
        end
        Done         = False$
        GaNStageInfo = Database_Services('ReadDataRow', 'APP_INFO', 'GAN_STAGES')
        For each StageRow in GanStageInfo using @FM setting fPos
            Locate StageKey in StageRow using @VM setting vPos then
                StageIndex = fPos
                Response   = StageIndex
                Done       = True$
            end
        Until Done EQ True$
        Next StageRow
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// GetRunType
//
// Input:
//      StageID   - [Required]
//          or
//      StageDesc - [Required]
//
// Output:
//      Returns the GaN run type (i.e. CASS or WFR) associated with the given stage description/ID.
//
//----------------------------------------------------------------------------------------------------------------------
Service GetRunType(StageID, StageDesc)
    
    Response    = ''
    Done        = False$
    GaNStageInfo = Database_Services('ReadDataRow', 'APP_INFO', 'GAN_STAGES')
    Begin Case
        Case StageID NE ''
            For each StageRow in GanStageInfo using @FM setting fPos
                Locate StageID in StageRow using @VM setting vPos then
                    RunType  = StageRow<0, 4>
                    Response = RunType
                    Done     = True$
                end
            Until Done EQ True$
            Next StageRow
        Case StageDesc NE ''
            For each StageRow in GanStageInfo using @FM setting fPos
                Locate StageDesc in StageRow using @VM setting vPos then
                    RunType  = StageRow<0, 4>
                    Response = RunType
                    Done     = True$
                end
            Until Done EQ True$
            Next StageRow
    End Case
    
end service


//----------------------------------------------------------------------------------------------------------------------
// GetStageType
//
// Input:
//      StageID   - [Required]
//          or
//      StageDesc - [Required]
//
// Output:
//      Returns the GaN stage type (i.e. Reactor, Metrology, or Disposition) associated with the given stage
//      description/ID.
//
//----------------------------------------------------------------------------------------------------------------------
Service GetStageType(StageID, StageDesc)
    
    Response    = ''
    Done        = False$
    GaNStageInfo = Database_Services('ReadDataRow', 'APP_INFO', 'GAN_STAGES')
    Begin Case
        Case StageID NE ''
            For each StageRow in GanStageInfo using @FM setting fPos
                Locate StageID in StageRow using @VM setting vPos then
                    StageType = StageRow<0, 5>
                    Response  = StageType
                    Done      = True$
                end
            Until Done EQ True$
            Next StageRow
        Case StageDesc NE ''
            For each StageRow in GanStageInfo using @FM setting fPos
                Locate StageDesc in StageRow using @VM setting vPos then
                    StageType = StageRow<0, 5>
                    Response  = StageType
                    Done      = True$
                end
            Until Done EQ True$
            Next StageRow
    End Case
    
end service


//----------------------------------------------------------------------------------------------------------------------
// GetCassStages
//
// Input:
//      ReactRunID - [Required]
//
// Output:
//      Response - @VM delimited array of GaN cassette-level stages associated with the given REACT_RUN ID.
//
//----------------------------------------------------------------------------------------------------------------------
Service GetCassStages(ReactRunID)
    
    Response = ''
    PSNo     = Xlate('REACT_RUN', ReactRunID, 'PS_NO', 'X')
    If PSNo NE '' then
        GaNStageInfo = Database_Services('ReadDataRow', 'APP_INFO', 'GAN_STAGES') 
        PRSStageKeys = Xlate('PROD_SPEC', PSNo, 'PRS_STAGE_KEY', 'X')
        CassStages   = ''
        For each PRSStageKey in PRSStageKeys using @VM
            StageID = Field(PRSStageKey, '*', 2)
            Done    = False$
            For each GaNStageRow in GaNStageInfo using @FM setting fPos
                Locate StageID in GaNStageRow using @VM setting vPos then
                    RunType = GaNStageRow<0, 4>
                    If RunType EQ 'CASS' then
                        CassStages<1, fPos> = StageID
                        Done = True$
                    end
                end
            Until Done EQ True$
            Next GaNStageRow
        Next PRSStageKey
        Convert @VM to ' ' in CassStages
        CassStages = Trim(CassStages)
        Convert ' ' to @VM in CassStages
        Response = CassStages
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// GetWfrStages
//
// Input:
//      ReactRunID - [Required]
//
// Output:
//      Response - @VM delimited array of GaN wafer-level stages associated with the given REACT_RUN ID.
//
//----------------------------------------------------------------------------------------------------------------------
Service GetWfrStages(ReactRunID)
    
    Response = ''
    PSNo     = Xlate('REACT_RUN', ReactRunID, 'PS_NO', 'X')
    If PSNo NE '' then
        GaNStageInfo = Database_Services('ReadDataRow', 'APP_INFO', 'GAN_STAGES') 
        PRSStageKeys = Xlate('PROD_SPEC', PSNo, 'PRS_STAGE_KEY', 'X')
        WfrStages    = ''
        For each PRSStageKey in PRSStageKeys using @VM
            StageID = Field(PRSStageKey, '*', 2)
            Done    = False$
            For each GaNStageRow in GaNStageInfo using @FM setting fPos
                Locate StageID in GaNStageRow using @VM setting vPos then
                    RunType = GaNStageRow<0, 4>
                    If RunType EQ 'WFR' then
                        WfrStages<1, fPos> = StageID
                        Done = True$
                    end
                end
            Until Done EQ True$
            Next GaNStageRow
        Next PRSStageKey
        Convert @VM to ' ' in WfrStages
        WfrStages = Trim(WfrStages)
        Convert ' ' to @VM in WfrStages
        Response = WfrStages
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// GetGaNStages
//
//
//----------------------------------------------------------------------------------------------------------------------
Service GetGaNStages(StageType=STAGETYPE, PSNo)
    
    If StageType NE '' then
        StageOrderKeys = ''
        If PSNo NE '' then StageOrderKeys = Xlate('PROD_SPEC', PSNo, 'PRS_STAGE_ROUTE', 'X')
        If StageOrderKeys EQ '' then
            // Use default stage configuration because no route is defined in the PSN   
            GaNStageArray = Database_Services('ReadDataRow', 'APP_INFO', 'GAN_STAGES')
            For each Row in GaNStageArray using @FM setting RowIndex
                RunType = Row<0, 5>
                If RunType EQ StageType then Response<0, -1> = Row<0, 1>
            Next Row
        end else
            StageOrderKeys = SRP_Array('Rotate', StageOrderKeys, @VM, '*')
            StageOrderList = StageOrderKeys<0, 1>
            Swap '*' with @VM in StageOrderList
            For each StageID in StageOrderList using @VM
                ThisStageType = GaN_Services('GetStageType', StageID)
                If ThisStageType EQ StageType then Response<0, -1> = StageID
            Next StageID
        end
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// CreateEtchRun
//
//
//----------------------------------------------------------------------------------------------------------------------
Service CreateEtchRun(Reactor)
    
    // Place Etch ID into GaN WIP under LOCATION*GCH*Q_ETCH
    CurrDTM                         = DateTime()
    LocID                           = 'GCH*Q_ETCH'
    EtchID                          = NextKey('GAN_ETCH')
    ToolID                          = 'R':Reactor 							  
    GanEtchRec                      = ''
    GanEtchRec<GAN_ETCH_REACTOR$>   = Reactor
    GanEtchRec<GAN_ETCH_USER$>      = @USER4
    GanEtchRec<GAN_ETCH_AVAILABLE$> = True$													
    GanEtchRec<GAN_ETCH_LOC_DTM$>   = CurrDTM
    GanEtchRec<GAN_ETCH_LOC_BY$>    = @User4
    GanEtchRec<GAN_ETCH_LOC_EVENT$> = 'CREATE'
    GanEtchRec<GAN_ETCH_TOOL_ID$>   = ToolID
    GanEtchRec<GAN_ETCH_INV_LOC$>   = LocID   
    Database_Services('WriteDataRow', 'GAN_ETCH', EtchID, GanEtchRec, True$, False$, True$)
    LocRec                          = Database_Services('ReadDataRow', 'LOCATION', LocID)
    LocQ                            = LocRec<LOCATION_ETCH_ID$>
    DisplayEtchID                   = Xlate('GAN_ETCH', EtchID, 'DISPLAY_ETCH_ID', 'X')
    Locate DisplayEtchID in LocQ using @VM setting vPos else
        LocQ<1,-1>                = DisplayEtchID
        LocRec<LOCATION_ETCH_ID$> = LocQ
        Database_Services('WriteDataRow', 'LOCATION', LocID, LocRec, True$, False$, True$)
    end
    Response = EtchID
    
end service


//----------------------------------------------------------------------------------------------------------------------
// StartEtchRun
//
//
//----------------------------------------------------------------------------------------------------------------------
Service StartEtchRun(EtchID)

    CurrDTM                             = DateTime()
    GanEtchRec                          = Database_Services('ReadDataRow', 'GAN_ETCH', EtchID)
    Reactor                             = GanEtchRec<GAN_ETCH_REACTOR$>
    ToolID                              = 'R':Reactor
    GanEtchRec<GAN_ETCH_START_DTM$>     = DateTime()
    GanEtchRec<GAN_ETCH_LOC_DTM$, -1>   = CurrDTM
    GanEtchRec<GAN_ETCH_LOC_BY$, -1>    = @User4
    GanEtchRec<GAN_ETCH_LOC_EVENT$, -1> = 'START'
    GanEtchRec<GAN_ETCH_TOOL_ID$, -1>   = ToolID
    Database_Services('WriteDataRow', 'GAN_ETCH', EtchID, GanEtchRec, True$, False$, True$)						   
    DisplayEtchID                       = Xlate('GAN_ETCH', EtchID, 'DISPLAY_ETCH_ID', 'X')															   
    // Remove from Location queue
    LocID                               = 'GCH*Q_ETCH' 
    LocRec                              = Database_Services('ReadDataRow', 'LOCATION', LocID)
    LocQ                                = LocRec<LOCATION_WFR_ID$>
    Locate DisplayEtchID in LocQ using @VM setting vPos then
        LocQ                      = Delete(LocQ, 1, vPos, 0)
        LocRec<LOCATION_ETCH_ID$> = LocQ
        Database_Services('WriteDataRow', 'LOCATION', LocID, LocRec, True$, False$, True$)
    end
    // Add to Tool queue
    ToolEtchRec = Database_Services('ReadDataRow', 'TOOL_ETCH', ToolID)
    ToolQ       = ToolEtchRec<TOOL_ETCH.ETCH_ID$>
    Locate DisplayEtchID in ToolQ using @VM setting vPos else
        ToolQ<1,-1>                     = DisplayEtchID
        ToolEtchRec<TOOL_ETCH.ETCH_ID$> = ToolQ
        Database_Services('WriteDataRow', 'TOOL_ETCH', ToolID, ToolEtchRec, True$, False$, True$)
    end
          
end service


//----------------------------------------------------------------------------------------------------------------------
// StopEtchRun
//
// Input:
//      EtchID - [Required]
//
//----------------------------------------------------------------------------------------------------------------------
Service StopEtchRun(EtchID)
    
    CurrDTM                             = DateTime()
    Reactor                             = Xlate('GAN_ETCH', EtchID, GAN_ETCH_REACTOR$, 'X')
    ToolID                              = 'R':Reactor
    GanEtchRec                          = Database_Services('ReadDataRow', 'GAN_ETCH', EtchID)
    GanEtchRec<GAN_ETCH_END_DTM$>       = DateTime()
    GanEtchRec<GAN_ETCH_AVAILABLE$>     = True$
    GanEtchRec<GAN_ETCH_LOC_DTM$, -1>   = CurrDTM
    GanEtchRec<GAN_ETCH_LOC_BY$, -1>    = @User4
    GanEtchRec<GAN_ETCH_LOC_EVENT$, -1> = 'STOP'
    GanEtchRec<GAN_ETCH_TOOL_ID$, -1>   = ToolID
    Database_Services('WriteDataRow', 'GAN_ETCH', EtchID, GanEtchRec, True$, False$, True$)												 
    GaN_Services('RemoveEtchFromWIP', EtchID)
        
end service


//----------------------------------------------------------------------------------------------------------------------
// DeleteEtchRun
//
//
//----------------------------------------------------------------------------------------------------------------------
Service DeleteEtchRun(EtchID)
    			
    GaN_Services('RemoveEtchFromWIP', EtchID)
    Database_Services('DeleteDataRow', 'GAN_ETCH', EtchID, True$, False$)
    
end service


//----------------------------------------------------------------------------------------------------------------------
// MoveCassToQueue
//
// Input:
//      WOMatKey - [Required]
//      QueueID  - [Required]
//
// Output:
//      Response - 
//
//----------------------------------------------------------------------------------------------------------------------
Service MoveCassToQueue(WOMatKey, QueueID)
    
    Success = False$
      
    If WOMatKey NE '' and QueueID NE '' then
        QType = GaN_Services('GetQueueType', QueueID)
        If QType EQ 'Location' then
            QRec = Database_Services('ReadDataRow', 'LOCATION', QueueID)
            CassQ = QRec<LOCATION_CASS_ID$>
            Locate WOMatKey in CassQ using @VM setting vPos else
                CassQ<1, -1> = WOMatKey
                QRec<LOCATION_CASS_ID$> = CassQ
                Database_Services('WriteDataRow', 'LOCATION', QueueID, QRec, True$, False$, True$)
                Success = True$
            end
        end else
            Error_Services('Add', 'Queue ID of type "Location" not supplied')
        end
    end
    
    Response = Success
    
end service


//----------------------------------------------------------------------------------------------------------------------
// RemoveCassFromQueue
//
// Input:
//      WOMatKey - [Required]
//      QueueID  - [Required]
//
// Output:
//      Response - 
//
//----------------------------------------------------------------------------------------------------------------------
Service RemoveCassFromQueue(WOMatKey, QueueID)

    Success = False$
    
    If WOMatKey NE '' and QueueID NE '' then
        QType = GaN_Services('GetQueueType', QueueID)
        If QType EQ 'Location' then
            QRec = Database_Services('ReadDataRow', 'LOCATION', QueueID)
            CassQ = QRec<LOCATION_CASS_ID$>
            Locate WOMatKey in CassQ using @VM setting vPos then
                CassQ = Delete(CassQ, 1, vPos, 0)
                QRec<LOCATION_CASS_ID$> = CassQ
                Database_Services('WriteDataRow', 'LOCATION', QueueID, QRec, True$, False$, True$)
                Success = True$
            end
        end else
            Error_Services('Add', 'Queue ID of type "Location" not supplied')
        end
        
    end
    
    Response = Success
    
end service


//----------------------------------------------------------------------------------------------------------------------
// GetCassQueue
//
// Input:
//      WOMatKey    - [Required]
//
// Output:
//      Response - LOCATION or TOOL_WFR queue ID
//
//----------------------------------------------------------------------------------------------------------------------
Service GetCassQueue(WOMatKey)

    Done      = False$
    CassQ     = ''
    GaNStages = Database_Services('ReadDataRow', 'APP_INFO', 'GAN_STAGES')
    GaNStages = SRP_Array('Rotate', GaNStages, @FM, @VM)
    // Need to scan all LOCATION queues for the cassette
    LocQs       = GaNStages<6>
    ToolClasses = GaNStages<3>
    For each LocQKey in LocQs using @VM setting vPos
        If LocQKey NE '' then
            LocQRec      = Database_Services('ReadDataRow', 'LOCATION', LocQKey)
            CassettesInQ = LocQRec<LOCATION_CASS_ID$>
            Locate WOMatKey in CassettesInQ using @VM setting wPos then
                Done  = True$
                CassQ = LocQKey
            end
        end
    Until Done EQ True$
    Next LocQRec
    Response   = CassQ    
    
end service


//----------------------------------------------------------------------------------------------------------------------
// RemoveCassFromWIP
//
// Input:
//      WOMatKey - [Required]
//
// Output:
//      Response - True$ (1) if the WOMatKey was removed from the WIP, False$ (0) otherwise.
//
//----------------------------------------------------------------------------------------------------------------------
Service RemoveCassFromWIP(WOMatKey)

    Success    = False$
    If WOMatKey NE '' then
        CassQueue   = GaN_Services('GetCassQueue', WOMatKey)
        CassInQueue = (CassQueue NE '')
        If CassInQueue EQ True$ then
            NumAttempts = 0
            Loop           
                Error_Services('Clear')
                // Location queues exist perpetually, so no need to check if the row exists.
                QRec = Database_Services('ReadDataRow', 'LOCATION', CassQueue)
                LocQ = QRec<LOCATION_CASS_ID$>
                Locate WOMatKey in LocQ using @VM setting vPos then
                    LocQ = Delete(LocQ, 0, vPos, 0)
                    QRec<LOCATION_CASS_ID$> = LocQ
                    Database_Services('WriteDataRow', 'LOCATION', CassQueue, QRec, True$, False$, True$)
                    Success = True$
                end else
                    Error_Services('Add', 'Error locating Cass ID ':WOMatKey:' in location queue ':LocQ:'.')
                end              

                NumAttempts += 1
                CassQueue   = GaN_Services('GetCassQueue', WOMatKey)
                CassInQueue = (CassQueue NE '')
            Until ( (CassInQueue EQ False$) or (NumAttempts GT 5) )
            Repeat
            
            LogData    = ''
            LogData<1> = LoggingDTM
            LogData<2> = @USER4
            LogData<3> = WOMatKey
            LogData<4> = CassQueue
            LogData<5> = CassQueue
            If Success EQ True$ then
                LogData<6> = 'Successfully removed Cass ID ':WOMatKey: ' from queue ':CassQueue:'. Number of attempts: ':NumAttempts:'.'
                Logging_Services('AppendLog', objWIPLog, LogData, @RM, @FM, False$)  
            end else            
                If Error_Services('HasError') then
                    LogData<6> = Error_Services('GetMessage'):' Number of attempts: ':NumAttempts:'.'
                end else
                    LogData<6> = 'Error removing Cass ID ':WOMatKey:' from queue ':CassQueue:'. Number of attempts: ':NumAttempts:'.'
                end           
                Logging_Services('AppendLog', objWIPLog, LogData, @RM, @FM, False$)       
            end
        end else
            // Cass not in queue that was passed in. Log this information.
            LogData    = ''
            LogData<1> = LoggingDTM
            LogData<2> = @USER4
            LogData<3> = WOMatKey
            LogData<4> = CassQueue
            LogData<5> = CassQueue
            LogData<6> = 'Error in ':Service:' service. Cass not in QueueID ':CassQueue:'.'
            Logging_Services('AppendLog', objWIPLog, LogData, @RM, @FM, False$)           
        end
    end else
        // WOMatKey is null
        LogData    = ''
        LogData<1> = LoggingDTM
        LogData<2> = @USER4
        LogData<3> = WOMatKey
        LogData<4> = CassQueue
        LogData<5> = CassQueue
        LogData<6> = 'Error in ':Service:' service. WOMatKey is null.'
        Logging_Services('AppendLog', objWIPLog, LogData, @RM, @FM, False$)
    end
    Response = Success
    
end service


//----------------------------------------------------------------------------------------------------------------------
// MoveWfrToQueue
//
// Input:
//      WOWfrID - [Required]
//      QueueID - [Required]
//
// Output:
//      Response - True$ (1) if successful, False$ (0) otherwise.
//
//----------------------------------------------------------------------------------------------------------------------
Service MoveWfrToQueue(WOWfrID, QueueID)

    Success = False$
      
    If WOWfrID NE '' and QueueID NE '' then
        QType = GaN_Services('GetQueueType', QueueID)
        Begin Case
            Case QType EQ 'Tool'
                If RowExists('TOOL_WFR', QueueID) then
                    // Tool Queue already exists, so append wafer to the end of the queue
                    QRec = Database_Services('ReadDataRow', 'TOOL_WFR', QueueID)
                    ToolQ = QRec<TOOL_WFR_WFR_ID$>
                    Locate WOWfrID in ToolQ using @VM setting vPos else
                        ToolQ<1, -1> = WOWfrID
                        QRec<TOOL_WFR_WFR_ID$> = ToolQ
                        Database_Services('WriteDataRow', 'TOOL_WFR', QueueID, QRec, True$, False$, True$)
                        Success = True$
                    end
                end else
                    // Tool Queue is empty (i.e. the record does not exists), so create the TOOL_WFR record (i.e. TOOL Queue).
                    QRec = ''
                    QRec<TOOL_WFR_WFR_ID$> = WOWfrID
                    Database_Services('WriteDataRow', 'TOOL_WFR', QueueID, QRec, True$, False$, True$)
                    Success = True$               
                end
            Case QType EQ 'Location'
                // Location queues exist perpetually, so no need to check if the row exists.
                QRec = Database_Services('ReadDataRow', 'LOCATION', QueueID)
                LocQ = QRec<LOCATION_WFR_ID$>
                Locate WOWfrID in LocQ using @VM setting vPos else
                    LocQ<0, -1> = WOWfrID
                    QRec<LOCATION_WFR_ID$> = LocQ
                    Database_Services('WriteDataRow', 'LOCATION', QueueID, QRec, True$, False$, True$)
                    Success = True$
                end                
            Case Otherwise$
                Error_Services('Add', 'Unrecognized QueueID ':QueueID:' of type ':QType:' supplied.')
        End Case
    end
    
    Response = Success
    
end service


//----------------------------------------------------------------------------------------------------------------------
// RemoveWfrFromWIP
//
// Input:
//      WfrID - [Required]
//
// Output:
//      Response - True$ (1) if successful, False$ (0) otherwise.
//
//----------------------------------------------------------------------------------------------------------------------
Service RemoveWfrFromWIP(WfrID)

    Success    = False$
    WfrQueue   = ''
    If WfrID NE '' then
        WfrQueue     = Gan_Services('GetWfrQueue', WfrID)
        WfrQueueOrig = WfrQueue
        WfrInQueue   = (WfrQueue NE '')
        If WfrInQueue EQ True$ then
            NumAttempts = 0
            Loop           
                Error_Services('Clear')
                QType = GaN_Services('GetQueueType', WfrQueue)
                Begin Case
                    Case QType EQ 'Tool'
                        If RowExists('TOOL_WFR', WfrQueue) then
                            QRec = Database_Services('ReadDataRow', 'TOOL_WFR', WfrQueue)
                            ToolQ = QRec<TOOL_WFR_WFR_ID$>
                            Locate WfrID in ToolQ using @VM setting vPos then
                                ToolQ = Delete(ToolQ, 0, vPos, 0)
                                QRec<TOOL_WFR_WFR_ID$> = ToolQ
                                Database_Services('WriteDataRow', 'TOOL_WFR', WfrQueue, QRec, True$, False$, True$)
                                Success = True$
                            end else
                                Error_Services('Add', 'Error locating wafer ID ':WfrID:' in tool queue ':ToolQ:'.')
                            end
                        end else
                            // Tool Queue is empty (i.e. the record does not exists), so return error.
                            Error_Services('Add', 'Cannot remove WO_WFR: ':WfrID:' from the TOOL_WFR queue ':WfrQueue |
                                          ' because it is empty.')       
                        end                
                    Case QType EQ 'Location'
                        // Location queues exist perpetually, so no need to check if the row exists.
                        QRec = Database_Services('ReadDataRow', 'LOCATION', WfrQueue)
                        LocQ = QRec<LOCATION_WFR_ID$>
                        Locate WfrID in LocQ using @VM setting vPos then
                            LocQ = Delete(LocQ, 0, vPos, 0)
                            QRec<LOCATION_WFR_ID$> = LocQ
                            Database_Services('WriteDataRow', 'LOCATION', WfrQueue, QRec, True$, False$, True$)
                            Success = True$
                        end else
                            Error_Services('Add', 'Error locating wafer ID ':WfrID:' in location queue ':LocQ:'.')
                        end
                    Case Otherwise$
                        Error_Services('Add', 'Unrecognized QueueID ':WfrQueue:' of type ':QType:' supplied.')               
                End Case
                NumAttempts += 1
                WfrQueue     = Gan_Services('GetWfrQueue', WfrID)
                WfrInQueue   = (WfrQueue NE '')
            Until ( (WfrInQueue EQ False$) or (NumAttempts GT 5) )
            Repeat           
            LogData    = ''
            LogData<1> = LoggingDTM
            LogData<2> = @USER4
            LogData<3> = WfrID
            LogData<4> = ''
            LogData<5> = WfrQueueOrig
            If Success EQ True$ then
                LogData<6> = 'Successfully removed wafer ID ':WfrID: ' from queue ':WfrQueueOrig:'. Number of attempts: ':NumAttempts:'.'
                Logging_Services('AppendLog', objWIPLog, LogData, @RM, @FM, False$)  
            end else            
                If Error_Services('HasError') then
                    LogData<6> = Error_Services('GetMessage'):' Number of attempts: ':NumAttempts:'.'
                end else
                    LogData<6> = 'Error removing wafer ID ':WfrID:' from queue ':WfrQueueOrig:'. Number of attempts: ':NumAttempts:'.'
                end           
                Logging_Services('AppendLog', objWIPLog, LogData, @RM, @FM, False$)       
            end
        end else
            LogData    = ''
            LogData<1> = LoggingDTM
            LogData<2> = @USER4
            LogData<3> = WfrID
            LogData<4> = ''
            LogData<5> = ''
            LogData<6> = 'Error in ':Service:' service. Wafer ID ':WfrID:' not found in any queue.'
            Logging_Services('AppendLog', objWIPLog, LogData, @RM, @FM, False$)          
        end
    end else
        // WfrID is null. This is likely from routines trying to remove wafers after the RDS has been
        // closed the wafers are off of the WIP.
        LogData    = ''
        LogData<1> = LoggingDTM
        LogData<2> = @USER4
        LogData<3> = WfrID
        LogData<4> = ''
        LogData<5> = ''
        LogData<6> = 'Error in ':Service:' service. WfrID is null.'
        Logging_Services('AppendLog', objWIPLog, LogData, @RM, @FM, False$)
    end
    Response = Success
    
end service


Service RemoveEtchFromWIP(EtchID)
    
    Success   = False$
    EtchQueue = ''
    If EtchID NE '' then
        DisplayEtchID = Xlate('GAN_ETCH', EtchID, 'DISPLAY_ETCH_ID', 'X')
        EtchQueue     = Gan_Services('GetEtchQueue', DisplayEtchID)
        EtchQueueOrig = EtchQueue
        EtchInQueue   = (EtchQueue NE '')
        If EtchInQueue EQ True$ then
            NumAttempts = 0
            Loop           
                QType = GaN_Services('GetQueueType', EtchQueue)
                Begin Case
                    Case QType EQ 'Tool'
                        If RowExists('TOOL_ETCH', EtchQueue) then
                            QRec = Database_Services('ReadDataRow', 'TOOL_ETCH', EtchQueue)
                            ToolQ = QRec<TOOL_ETCH.ETCH_ID$>
                            Locate DisplayEtchID in ToolQ using @VM setting vPos then
                                ToolQ                    = Delete(ToolQ, 0, vPos, 0)
                                QRec<TOOL_ETCH.ETCH_ID$> = ToolQ
                                Database_Services('WriteDataRow', 'TOOL_ETCH', EtchQueue, QRec, True$, False$, True$)
                                Success                  = True$
                            end else
                                Error_Services('Add', 'Error locating etch ID ':DisplayEtchID:' in tool queue ':ToolQ:'.')
                            end
                        end else
                            // Tool Queue is empty (i.e. the record does not exists), so return error.
                            Error_Services('Add', 'Cannot remove etch ID ':DisplayEtchID:' from the TOOL_WFR queue ':EtchQueue |
                                          ' because it is empty.')       
                        end                
                    Case QType EQ 'Location'
                        // Location queues exist perpetually, so no need to check if the row exists.
                        QRec = Database_Services('ReadDataRow', 'LOCATION', EtchQueue)
                        LocQ = QRec<LOCATION_ETCH_ID$>
                        Locate DisplayEtchID in LocQ using @VM setting vPos then
                            LocQ                    = Delete(LocQ, 0, vPos, 0)
                            QRec<LOCATION_ETCH_ID$> = LocQ
                            Database_Services('WriteDataRow', 'LOCATION', EtchQueue, QRec, True$, False$, True$)
                            Success                 = True$
                        end else
                            Error_Services('Add', 'Error locating etch ID ':DisplayEtchID:' in location queue ':LocQ:'.')
                        end
                    Case Otherwise$
                        Error_Services('Add', 'Unrecognized QueueID ':EtchQueue:' of type ':QType:' supplied.')               
                End Case
                NumAttempts += 1
                EtchQueue    = Gan_Services('GetEtchQueue', DisplayEtchID)
                EtchInQueue  = (EtchQueue NE '')
            Until ( (EtchInQueue EQ False$) or (NumAttempts GT 5) )
            Repeat
            
            LogData    = ''
            LogData<1> = LoggingDTM
            LogData<2> = @USER4
            LogData<3> = DisplayEtchID
            LogData<4> = ''
            LogData<5> = EtchQueueOrig
            If Success EQ True$ then
                LogData<6> = 'Successfully removed etch ID ':DisplayEtchID: ' from queue ':EtchQueueOrig:'. Number of attempts: ':NumAttempts:'.'
                Logging_Services('AppendLog', objWIPLog, LogData, @RM, @FM, False$)  
            end else            
                If Error_Services('HasError') then
                    LogData<6> = Error_Services('GetMessage'):' Number of attempts: ':NumAttempts:'.'
                end else
                    LogData<6> = 'Error removing etch ID ':DisplayEtchID:' from queue ':EtchQueueOrig:'. Number of attempts: ':NumAttempts:'.'
                end           
                Logging_Services('AppendLog', objWIPLog, LogData, @RM, @FM, False$)       
            end
        end else
            LogData    = ''
            LogData<1> = LoggingDTM
            LogData<2> = @USER4
            LogData<3> = DisplayEtchID
            LogData<4> = ''
            LogData<5> = ''
            LogData<6> = 'Error in ':Service:' service. Etch ID ':DisplayEtchID:' not found in any queue.'
            Logging_Services('AppendLog', objWIPLog, LogData, @RM, @FM, False$)           
        end
    end else
        LogData    = ''
        LogData<1> = LoggingDTM
        LogData<2> = @USER4
        LogData<3> = DisplayEtchID
        LogData<4> = ''
        LogData<5> = ''
        LogData<6> = 'Error in ':Service:' service. DisplayEtchID is null.'
        Logging_Services('AppendLog', objWIPLog, LogData, @RM, @FM, False$)
    end
    Response = Success
    
    
end service


//----------------------------------------------------------------------------------------------------------------------
// GetQueueType
//
// Input:
//      QueueID - [Required]
//
// Output:
//      Response - Queue type - 'Location' or 'Tool'
//
//----------------------------------------------------------------------------------------------------------------------
Service GetQueueType(QueueID)
    
    QueueType = ''
    NumFields = Count(QueueID, '*')
    If NumFields EQ 0 then
        QueueType = 'Tool'
    end else
        QueueType = 'Location'
    end
    Response = QueueType
    
end service


//----------------------------------------------------------------------------------------------------------------------
// GetLocQueueID
//
// Input:
//      StageID - [Required]
//
// Output:
//      Response - LOCATION QueueID
//
//----------------------------------------------------------------------------------------------------------------------
Service GetLocQueueID(StageID)
    
    QueueID = ''
    Done      = False$
    GaNStages = Database_Services('ReadDataRow', 'APP_INFO', 'GAN_STAGES')
    GaNStages = SRP_Array('Rotate', GaNStages, @FM, @VM)
    LocQs       = GaNStages<6>
    StageIDs    = GaNStages<1>
    Locate StageID in StageIDs using @VM setting vPos then
        QueueID = LocQs<0, vPos>
    end
    Response = QueueID
    
end service


//----------------------------------------------------------------------------------------------------------------------
// GetPreEpiWfrs
//
// Input:
//      WONo    - [Required]
//      NumWfrs - [Required]
//
// Output:
//      Response - NumWfrs of InWfrIDs if enough inbound material available, else null and error set.
//
//----------------------------------------------------------------------------------------------------------------------
Service GetPreEpiWfrs(WONo, NumWfrs)
    
    Response       = ''
    ConsumedWfrCnt = 0
    If (WONo NE '') and (NumWfrs GT 0) then
        CassNos        = Xlate('WO_LOG', WONo, 'WO_MAT_CASS_NO', 'X')
        CassCnt        = DCount(CassNos, @VM)
        ConsumedWfrCnt = 0
        CassIndex      = 1
        Loop
            HaveCassettes = (CassIndex LE CassCnt)
        While HaveCassettes
            CassNo      = CassNos<1,CassIndex>            
            WOMatKey 	= WONo:'*':CassNo
            WOMatRec	= Database_Services('ReadDataRow', 'WO_MAT', WOMatKey)                        
            If (WOMatRec<WO_MAT_HOLD$> EQ '') or (WOMatRec<WO_MAT_HOLD$> EQ False$) then
                WOWfrIDs	= Xlate('WO_MAT_WFR', WONo:'*':CassNo, WO_MAT_WFR_IN_WFR_ID$, 'X')
                SlotCnt		= WOMatRec<WO_MAT_WAFER_QTY$>
                SlotIndex   = 1             
                Loop
                    Finished   = (SlotIndex GT SlotCnt) or (ConsumedWfrCnt GE NumWfrs) 
                While Not(Finished)                                                                    
                    If WOWfrIDs<1,SlotIndex> NE '' then 
                        SlotID          = WONo:'*':CassNo:'*':SlotIndex 
                        Response        = Insert(Response, 0, -1, 0, SlotID)
                        ConsumedWfrCnt += 1
                    end
                    SlotIndex += 1                           
                Repeat
            end             
            CassIndex += 1           
        Repeat
    end
    If ConsumedWfrCnt NE NumWfrs then
        Response = ''
        ErrorMessage = 'Not enough inbound material available.'
        Error_Services('Add', ErrorMessage)
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// UnloadSusceptor
//
// Input:
//      RDSNo   - [Required]
//
// Output:
//      Response - True$ (1) if successful, False$ (0) otherwise
//
//----------------------------------------------------------------------------------------------------------------------
Service UnloadSusceptor(RDSNo)

    Response = False$
    If RDSNo NE '' then
        CurrDTM     = DateTime()
        // Update REACT_RUN record   
        ReactRunRec = Database_Services('ReadDataRow', 'REACT_RUN', RDSNo)
        If ReactRunRec NE '' then 
            GaNRunID    = ReactRunRec<REACT_RUN_GAN_RUN_ID$>
            WONo        = ReactRunRec<REACT_RUN_WO_NO$>
            WOStep      = ReactRunRec<REACT_RUN_WO_STEP$>
            Reactor     = ReactRunRec<REACT_RUN_REACTOR$>
            InWfrIDs    = ReactRunRec<REACT_RUN_IN_WFR_ID$>
            CarrSlotNos = ReactRunRec<REACT_RUN_CARR_SLOT_NO$>
            ToolID      = 'R':Reactor
            NextLocQ    = 'GCH*Q_RATE'
            CarrSlotIDs = ''   
            For each CarrSlotNo in CarrSlotNos using @VM setting vPos
                If CarrSlotNo NE '' then
                    CarrSlotIDs<0, vPos> = RDSNo:'.':CarrSlotNo
                end
            Next CarrSlotNo
            ReactRunRec<REACT_RUN_CARR_SLOT_ID$> = CarrSlotIDs
            ReactRunRec<REACT_RUN_CARR_WFR_ID$>  = InWfrIDs
            Database_Services('WriteDataRow', 'REACT_RUN', RDSNo, ReactRunRec, True$, False$, True$)
            If Error_Services('NoError') then           
                // Update WO_WFR record(s) and move WO_WFR(s) from TOOL_WFR queue to G_RATE queue
                For each InWfrID in InWfrIDs using @VM setting vPos
                    If InWfrID NE '' then
                        WOWfrRec = Database_Services('ReadDataRow', 'WO_WFR', InWfrID)
                        If WOWfrRec NE '' then                            
                            LocDTMs = WOWfrRec<WO_WFR_LOC_DTM$>
                            NextIndex = DCount(LocDTMs, @VM) + 1
                            WOWfrRec<WO_WFR_LOC_DTM$, NextIndex>   = CurrDTM
                            WOWfrRec<WO_WFR_LOC_BY$, NextIndex>    = @USER4
                            WOWfrRec<WO_WFR_LOC_EVENT$, NextIndex> = 'UNLOAD'
                            WOWfrRec<WO_WFR_CARR_SLOT$, NextIndex> = CarrSlotIDs<0, vPos>
                            WOWfrRec<WO_WFR_INV_LOC$, NextIndex>   = NextLocQ
                            Database_Services('WriteDataRow', 'WO_WFR', InWfrID, WOWfrRec, True$, False$, True$)
                            GaN_Services('RemoveWfrFromWIP', WfrID)
                            GaN_Services('MoveWfrToQueue', InWfrID, NextLocQ)
                        end else
                            Error_Services('Add', 'Failed to read WO_WFR record ':InWfrID:' in ':Service:' service.')
                        end
                    end
                Next InWfrID              
                // Update RUN_STAGE record (i.e. sign UNLOAD IA and stage complete field)
                RunStageKey = RDSNo:'*GROWTH'
                RunStageRec = Database_Services('ReadDataRow', 'RUN_STAGE', RunStageKey)
                If RunStageRec NE '' then
                    InvActions  = RunStageRec<RUN_STAGE_SPEC_INV_ACTION$>
                    IACompBy    = RunStageRec<RUN_STAGE_SIA_COMP_BY$>
                    IACompDTM   = RunStageRec<RUN_STAGE_SIA_COMP_DTM$>
                    Locate 'UNLOAD' in InvActions using @VM setting vPos then
                        IACompBy<0, vPos>  = @User4
                        IACompDTM<0, vPos> = CurrDTM
                    end
                    RunStageRec<RUN_STAGE_SIA_COMP_BY$>  = IACompBy
                    RunStageRec<RUN_STAGE_SIA_COMP_DTM$> = IACompDTM
                    RunStageRec<RUN_STAGE_COMP_BY$>      = @User4
                    RunStageRec<RUN_STAGE_COMP_DTM$>     = CurrDTM
                    RunStageRec<RUN_STAGE_STATUS$>       = 'COMP'
                    Database_Services('WriteDataRow', 'RUN_STAGE', RunStageKey, RunStageRec, True$, False$, True$)
                end else
                    Error_Services('Add', 'Failed to read RUN_STAGE record ':RunStageKey:' in ':Service:' service.')
                end
            end else
                Error_Services('Add', 'Failed to write REACT_RUN record ':RDSNo:' in ':Service:' service.')
            end
        end else
            Error_Services('Add', 'Failed to read REACT_RUN record ':RDSNo:' in ':Service:' service.')
        end
        If Error_Services('NoError') then
            Response = True$
        end else
            ErrorMessage = 'Error in ':Service:' service. ':Error_Services('GetMessage')
            LogData      = ''
            LogData<1>   = LoggingDTM
            LogData<2>   = RDSNo
            LogData<3>   = ErrorMessage
            Logging_Services('AppendLog', objUnloadLog, LogData, @RM, @FM)
        end
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// GetNextStage
//
// Input:
//      WOWfrID   - [Required]
//
// Output:
//      Response - Next WFR_STAGE that is prescribed and has not yet been completed.
//
//----------------------------------------------------------------------------------------------------------------------
Service GetNextStage(WOWfrID)
    
    Convert '.' to '*' in WOWfrID
    CurrStage   = GaN_Services('GetCurrStage', WOWfrID)
    ReactRunKey = Xlate('WO_WFR', WOWfrID, 'RDS_NO', 'X')
    WfrStage    = Xlate('REACT_RUN', ReactRunKey, 'WFR_STAGE', 'X')
    FormatWfrID = WOWfrID
    Done        = False$
    Convert '*' to '.' in FormatWfrID
    Begin Case
        Case CurrStage EQ 'G_RATE'
            // Lookup first stage in WFR_STAGE column within associated REACT_RUN record            
            Response = WfrStage<0, 1>            
        Case Otherwise$
            For each Stage in WfrStage using @VM setting vPos
                RunStageWfrKey = ReactRunKey:'*':Stage:'*':FormatWfrID
                StageStatus = Xlate('RUN_STAGE_WFR', RunStageWfrKey, 'STATUS', 'X')
                If StageStatus _EQC 'INIT' then
                    Response = Stage
                    Done     = True$
                end
            Until Done EQ True$
            Next Stage           
    End Case
       
end service


//----------------------------------------------------------------------------------------------------------------------
// GetNextRxStage
//
// Input:
//      RDSNo     - [Required]
//      CurrStage - [Required]
//
// Output:
//      Response - Next WFR_STAGE that is prescribed in the REACT_RUN record
//
//----------------------------------------------------------------------------------------------------------------------
Service GetNextRxStage(RDSNo, CurrStage)

    Response = ''
    If ( (RDSNo NE '') and (CurrStage NE '') ) then
        ReactRunRec = Database_Services('ReadDataRow', 'REACT_RUN', RDSNo)
        StageRoute  = ReactRunRec<REACT_RUN_GAN_STAGE_ROUTE$>
        RxStages    = ReactRunRec<REACT_RUN_WFR_STAGE$>
        NextStage   = ''
        If StageRoute NE '' then
            Done = False$
            Locate CurrStage in StageRoute using @VM setting vPos then
                NumStages = DCount(StageRoute, @VM)
                For StageIndex = (vPos + 1) to NumStages
                    NextStage = StageRoute<0, StageIndex>
                    Locate NextStage in RxStages using @VM setting rPos then
                        Done = True$
                    end
                Until Done EQ True$
                Next StageIndex
                Response = NextStage
            end
        end else
            Locate CurrStage in RxStages using @VM setting vPos then
                NextStage = RxStages<0, vPos + 1>
                Response  = NextStage
            end
        end
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// GetCurrStage
//
// Input:
//      WOWfrID   - [Required]
//
// Output:
//      Response - Current queue a wafer is in.
//
//----------------------------------------------------------------------------------------------------------------------
Service GetCurrStage(WOWfrID)
    
    Response    = ''
    WOWfrRec    = Database_Services('ReadDataRow', 'WO_WFR', WOWfrID)
    InvLocs     = WOWfrRec<WO_WFR_INV_LOC$>
    ToolLocs    = WOWfrRec<WO_WFR_TOOL_ID$>
    NumInvLocs  = DCount(InvLocs, @VM)
    NumToolLocs = DCount(ToolLocs, @VM)
    CurrStep = Max(NumInvLocs, NumToolLocs)
    LastToolLoc = ToolLocs<0, CurrStep>
    LastInvLoc  = InvLocs<0, CurrStep>
    Begin Case
        Case LastToolLoc NE '' and LastInvLoc EQ ''
            ToolID = LastToolLoc
            Response = Gan_Services('GetStageFromToolID', ToolID)
        Case LastToolLoc EQ '' and LastInvLoc NE ''
            LocQ  = LastInvLoc
            Response = GaN_Services('GetStageID', '', LocQ)
        Case Otherwise$
            // Error
    End Case
    
end service


//----------------------------------------------------------------------------------------------------------------------
// GetStageFromToolID
//
// Input:
//      ToolID   - [Required]
//
// Output:
//      Response - Stage ID associated with the given Tool ID. 
//
//----------------------------------------------------------------------------------------------------------------------
Service GetStageFromToolID(ToolID)
    
    Stage = ''
    If ToolID NE '' then
        Query = 'SELECT TOOL_CLASS CONTAINING "G_"'
        GoSub ClearCursors
        Rlist(Query, Target_ActiveList$, '', '', '')
        EOF  = False$
        Done = False$
        If @RecCount then
            Loop
            While EOF EQ False$
                ReadNext ToolClassID else EOF = True$
                Tools = Xlate('TOOL_CLASS', ToolClassID, 'TOOL', 'X')
                If Indexc(Tools, ToolID, 1) GT 0 then
                    LocWH = Xlate('TOOL_CLASS', ToolClassID, 'QIN_WH', 'X')
                    Loc   = Xlate('TOOL_CLASS', ToolClassID, 'QIN_LOC', 'X')
                    LocQ = LocWH:'*':Loc
                    Stage = Gan_Services('GetStageID', '', LocQ)
                    Done  = True$
                end
            Until Done EQ True$
            Repeat
        end
        GoSub ClearCursors
    end
    Response = Stage
        
end service


//----------------------------------------------------------------------------------------------------------------------
// GetNextEtchRun
//
// Input:
//      RDSNo   - [Required]
//
// Output:
//      Response - True$ (1) if successful, False$ (0) otherwise
//
//----------------------------------------------------------------------------------------------------------------------
Service GetNextEtchRun(Reactor)
    
    Response = ''
    If Reactor NE '' then
        EtchIDs   = ''
        EtchQList = ''
        RowIndex  = 1
        GoSub ClearCursors
        Query  = 'SELECT GAN_ETCH WITH AVAILABLE EQ ':True$:' AND WITH END_DTM NE "" AND WITH REACTOR EQ ' |
               :Reactor:' BY REACTOR BY START_DTM'
        Rlist(Query, Target_ActiveList$, '', '', '')
        EOF = False$
        If @RecCount then
            ReadNext GaNEtchID then Response = GaNEtchID             
        end
    end
    GoSub ClearCursors
    
end service


//----------------------------------------------------------------------------------------------------------------------
// ConsumeIntEtchID
//
// Input:
//      IntEtchID  - [Required]
//
// Output:
//      Response - True$ (1) if successful, False$ (0) otherwise
//
//----------------------------------------------------------------------------------------------------------------------
Service ConsumeIntEtchID(IntEtchID)
    
    Response = False$
    If IntEtchID NE '' then
        IntEtchRec = Database_Services('ReadDataRow', 'GAN_ETCH', IntEtchID)
        If IntEtchRec NE '' then
            IntEtchRec<GAN_ETCH_AVAILABLE$> = False$
            Database_Services('WriteDataRow', 'GAN_ETCH', IntEtchID, IntEtchRec, True$, False$, True$)
            Response = True$
        end
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// CreateRunStageWfr
//
// Input:
//      RDSNo   - [Required]
//
// Output:
//      Response - True$ (1) if successful, False$ (0) otherwise
//
//----------------------------------------------------------------------------------------------------------------------
Service CreateRunStageWfr(RDSNo, Stage, WfrID)
    
    Response       = False$
    PSN            = Xlate('RDS', RDSNo, 'PROD_SPEC_ID', 'X')
    ReactRunRec    = Database_Services('ReadDataRow', 'REACT_RUN', RDSNo)
    WfrStages      = ReactRunRec<REACT_RUN_WFR_STAGE$>
    Locate Stage in WfrStages using @VM setting sPos else
        // Stage has not yet been added to the REACT_RUN WFR_STAGE field
        UnRxStage      = Gan_Services('GetUnRxStatus', RDSNo, Stage)
        If UnRxStage EQ True$ then      
            UnRxStages     = ReactRunRec<REACT_RUN_UNPRESCRIBED_WFR_STAGE$>
            GaNStageInfo   = Database_Services('ReadDataRow', 'APP_INFO', 'GAN_STAGES')
            GaNStageInfo   = SRP_Array('Rotate', GaNStageInfo, @FM, @VM)
            AllStages      = GaNStageInfo<1>
            NewStageIndex  = Gan_Services('GetStageIndex', Stage)
            Found          = False$   
            For PrevStageIndex = (NewStageIndex - 1) to 1 Step -1
                PrevStage = AllStages<0, PrevStageIndex>
                Locate PrevStage in WfrStages using @VM setting vPos then
                    WfrStages  = Insert(WfrStages, 0, vPos + 1, 0, Stage)
                    UnRxStages = Insert(UnRxStages, 0, vPos + 1, 0, True$)
                    Found      = True$
                end
            Until Found EQ True$
            Next PrevStageIndex
            If Found EQ False$ then
                WfrStages  = Insert(WfrStages, 0, 1, 0, Stage)
                UnRxStages = Insert(UnRxStages, 0, 1, 0, True$)
            end
            ReactRunRec<REACT_RUN_WFR_STAGE$>              = WfrStages
            ReactRunRec<REACT_RUN_UNPRESCRIBED_WFR_STAGE$> = UnRxStages
            Database_Services('WriteDataRow', 'REACT_RUN', RDSNo, ReactRunRec, True$, False$, True$)
        end
    end
    RunStageWfrRec = ''   
    RunStageWfrRec<RUN_STAGE_WFR_SPEC_TOOL_CLASS$> = GaN_Services('GetToolClassIDs', Stage)
    RunStageWfrRec<RUN_STAGE_WFR_SPEC_INV_ACTION$> = GaN_Services('GetInvActions', Stage)  
    RunStageWfrRec<RUN_STAGE_WFR_STATUS$>          = 'INIT'

    WfrKID = WfrID
    // WfrID may be passed in using an asterisk as the Delimiter instead of a period
    // Convert asterisks to periods just to be safe
    Convert '*' to '.' in WfrKID
    RunStageWfrKey = RDSNo:'*':Stage:'*':WfrKID
    If Not(RowExists('RUN_STAGE_WFR', RunStageWfrKey)) then 
        Database_Services('WriteDataRow', 'RUN_STAGE_WFR', RunStageWfrKey, RunStageWfrRec, True$, False$, True$)
    end
    If Error_Services('NoError') then Response = True$
    
end service


//----------------------------------------------------------------------------------------------------------------------
// CreateAllRunStageWfr
//
// Input:
//      RDSNo       - [Required]
//      StopStageID - [Optional]
//
// Output:
//      Response - True$ (1) if successful, False$ (0) otherwise
//
//----------------------------------------------------------------------------------------------------------------------
Service CreateAllRunStageWfr(RDSNo, StopStageID, InWfrIDs)

    If (RDSNo NE '') then
        WfrStages      = Xlate('REACT_RUN', RDSNo, 'WFR_STAGE', 'X')
        UnRxStages     = Xlate('REACT_RUN', RDSNo, 'UNPRESCRIBED_WFR_STAGE', 'X')
        If StopStageID EQ '' then
            StopStageID = WfrStages[-1, 'B':@VM]
        end
        // If wafer IDs are not passed in, then the code assumes all prescribed stages are to be
        // created if they have not yet already been created.
        If InWfrIDs EQ '' then InWfrIDs = Xlate('REACT_RUN', RDSNo, 'IN_WFR_ID', 'X')
        Response    = False$
        NumStages   = DCount(WfrStages, @VM)
        NumWfrs     = DCount(InWfrIDs, @VM)
        WfrIndex    = 1
        Loop
            WfrID = InWfrIDs<0, WfrIndex>
            If WfrID NE '' then
                StageIndex     = 1
                FormattedWfrID = WfrID
                Convert '*' to '.' in FormattedWfrID
                Loop
                    Stage     = WfrStages<0, StageIndex>
                    UnRxStage = UnRxStages<0, StageIndex>
                    // Create RUN_STAGE_WFR record
                    RunStageWfrKey = RDSNo:'*':Stage:'*':FormattedWfrID
                    If Not(RowExists('RUN_STAGE_WFR', RunStageWfrKey)) and (UnRxStage EQ '') then
                        GaN_Services('CreateRunStageWfr', RDSNo, Stage, WfrID)
                    end
                Until Stage EQ StopStageID
                    StageIndex += 1
                Repeat
            end
            WfrIndex += 1
        Until WfrIndex GT NumWfrs
        Repeat
    end
    If Error_Services('NoError') then Response = True$
    
end service


//----------------------------------------------------------------------------------------------------------------------
// DispositionWfr
//
// Input:
//      RDSNo       - [Required]
//      WfrID       - [Required]
//
// Output:
//      Response - True$ (1) if successful, False$ (0) otherwise
//
//----------------------------------------------------------------------------------------------------------------------
Service DispositionWfr(RDSNo, WfrID, Auto)

    Response    = False$
    If (RDSNo NE '') and (WfrID NE '') then
        ReadyToSplit = Gan_Services('ReadyToSplit', WfrID)
        If ReadyToSplit EQ True$ then
            If Auto NE '' then
                User = 'AUTO'
            end else
                User = @User4
            end
            CurrDTM    = Datetime()
            PSNo       = Xlate('REACT_RUN', RDSNo, 'PS_NO', 'X')
            ANKO       = Xlate('PROD_SPEC', PSNo, 'ANKO', 'X')
            If ANKO EQ True$ then
                DispStages = 'DISP':@VM:'RETAIN'           
            end else
                DispStages = 'DISP':@VM:'G_PACK':@VM:'RETAIN'           
            end            
            For each Stage in DispStages using @VM setting vPos
                FormattedWfrID = WfrID
                Convert '*' to '.' in FormattedWfrID
                RunStageWfrKey = RDSNo:'*':Stage:'*':FormattedWfrID
                If Not(RowExists('RUN_STAGE_WFR', RunStageWfrKey)) then
                    GaN_Services('CreateRunStageWfr', RDSNo, Stage, WfrID)
                end
            Next Stage
            // Update RUN_STAGE_WFR record (Set Status to 'COMP', Fill in CompBy and CompDTM)
            FormatWfrID = WfrID
            Convert '*' to '.' in FormatWfrID
            RunStageWfrKey = RDSNo:'*SPLIT*':FormatWfrID
            RunStageWfrRec = Database_Services('ReadDataRow', 'RUN_STAGE_WFR', RunStageWfrKey)
            RunStageWfrRec<RUN_STAGE_WFR_COMP_BY$>  = User
            RunStageWfrRec<RUN_STAGE_WFR_COMP_DTM$> = CurrDTM
            RunStageWfrRec<RUN_STAGE_WFR_STATUS$>   = 'DISP'
            Database_Services('WriteDataRow', 'RUN_STAGE_WFR', RunStageWfrKey, RunStageWfrRec, True$, False$, True$)
            // Update the WO_WFR record with the LOC_BY, LOC_DTM, INV_LOC (the next prescribed location queue),
            // and LOC_EVENT (DISPOSITION)
            WOWfrKey = WfrID
            WOWfrRec = Database_Services('ReadDataRow', 'WO_WFR', WOWfrKey)
            LocDTMs  = WOWfrRec<WO_WFR_LOC_DTM$>
            NextStep = DCount(LocDTMs, @VM) + 1
            WOWfrRec<WO_WFR_LOC_DTM$, NextStep>     = CurrDTM
            WOWfrRec<WO_WFR_LOC_BY$, NextStep>      = User
            WOWfrRec<WO_WFR_LOC_EVENT$, NextStep>   = 'DISPOSITION'
            WOWfrRec<WO_WFR_TOOL_ID$, NextStep>     = ''
            CurrStage = Gan_Services('GetCurrStage', WfrID)
            CurrLocQ  = Gan_Services('GetLocQueueID', CurrStage)
            NextStage = Gan_Services('GetNextStage', WfrID)
            NextLocQ  = Gan_Services('GetLocQueueID', NextStage)
            WoWfrRec<WO_WFR_INV_LOC$, NextStep> = NextLocQ
            // Move wafer to the next prescribed location queue
            Gan_Services('RemoveWfrFromWIP', WfrID)
            Gan_Services('MoveWfrToQueue', WfrID, NextLocQ)
            Database_Services('WriteDataRow', 'WO_WFR', WOWfrKey, WOWfrRec, True$, False$, True$)
            // Update the Disposition DTM for all wafers that have been through or are at disposition
            ReactRunRec = Database_Services('ReadDataRow', 'REACT_RUN', RDSNo)
            InWfrIDs = ReactRunRec<REACT_RUN_IN_WFR_ID$>                    
            For each WOWfrID in InWfrIDs using @VM setting vPos
                ThisWOWfrRec = Database_Services('ReadDataRow', 'WO_WFR', WOWfrID)
                LocDTMs      = ThisWOWfrRec<WO_WFR_LOC_DTM$>
                LocQs        = ThisWOWfrRec<WO_WFR_INV_LOC$>
                Locate 'GGR*Q_DISP' in LocQs using @VM setting vPos then
                    // Wafer has been dispositioned at some point, so update the disposition DTM
                    LocDTMs<0, vPos> = CurrDTM
                    ThisWOWfrRec<WO_WFR_LOC_DTM$> = LocDTMs
                    Database_Services('WriteDataRow', 'WO_WFR', WOWfrID, ThisWOWfrRec, True$, False$, True$)
                end
            Next WOWfrID                       
        end
        If Error_Services('NoError') then Response = True$
    end else
        ErrorMsg = 'This wafer is not ready to split. One or more previous stages have not been completed.'
        Error_Services('Add', ErrorMsg)
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// StartWaferStage
//
//
//----------------------------------------------------------------------------------------------------------------------
Service StartWaferStage(RDSNo, WfrID, StageID, ToolID)
    
    // 1. Find which queue the wafers are currently in.
    // 2. Move wafers to the new queue.
    // 3. Update all RUN_STAGE_WFR records. (i.e. update the status from INIT to START)
    // 4. Update WO_WFR records for wafer trace purposes. (i.e. location DTM, event, queue, etc.)
    // 5. Refresh the UI to display the new wafer statuses.
    Response = False$
    If (RDSNo NE '') and (WfrID NE '') and (StageID NE '') and (ToolID NE '') then
        CurrDTM = Datetime()
*        LocQ = Gan_Services('GetWfrQueue', WfrID)
*        Gan_Services('RemoveWfrFromWIP', WfrID)
*        Gan_Services('MoveWfrToQueue', WfrID, ToolID)
        FormattedWfrID = WfrID
        Convert '*' to '.' in FormattedWfrID
        RunStageWfrKey = RDSNo:'*':StageID:'*':FormattedWfrID
        RunStageWfrRec = Database_Services('ReadDataRow', 'RUN_STAGE_WFR', RunStageWfrKey)
        InvAction      = 'START'
        RunStageWfrRec<RUN_STAGE_WFR_STATUS$> = 'START'
        // Sign InvAction, Fill InvAction DTM
        SpecInvActions = RunStageWfrRec<RUN_STAGE_WFR_SPEC_INV_ACTION$>
        SIACompBy      = RunStageWfrRec<RUN_STAGE_WFR_SIA_COMP_BY$>
        SIACompDTM     = RunStageWfrRec<RUN_STAGE_WFR_SIA_COMP_DTM$>
        Locate InvAction in SpecInvActions using @VM setting vPos then
            SIACompBy<0, vPos> = @USER4
            SIACompDTM<0, vPos> = CurrDTM
        end
        RunStageWfrRec<RUN_STAGE_WFR_SIA_COMP_BY$>  = SIACompBy
        RunStageWfrRec<RUN_STAGE_WFR_SIA_COMP_DTM$> = SIACompDTM
        Database_Services('WriteDataRow', 'RUN_STAGE_WFR', RunStageWfrKey, RunStageWfrRec, True$, False$, True$)
        WOWfrRec = Database_Services('ReadDataRow', 'WO_WFR', WfrID)
        LocDTM   = WOWfrRec<WO_WFR_LOC_DTM$>
        NextPos  = DCount(LocDTM, @VM) + 1
        WOWfrRec<WO_WFR_LOC_DTM$, NextPos>   = CurrDTM
        WOWfrRec<WO_WFR_LOC_BY$, NextPos>    = @USER4
        WOWfrRec<WO_WFR_LOC_EVENT$, NextPos> = 'START'
        WOWfrRec<WO_WFR_TOOL_ID$, NextPos>   = ToolID
        WOWfrRec<WO_WFR_INV_LOC$, NextPos>   = ''
        Database_Services('WriteDataRow', 'WO_WFR', WfrID, WOWfrRec, True$, False$, True$)
        If Error_Services('NoError') then Response = True$
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// StopWaferStage
//
//
//----------------------------------------------------------------------------------------------------------------------
Service StopWaferStage(RDSNo, WfrID, StageID)
    
    // 1. Find which queue the wafers are currently in.
    // 2. Move wafers to the new queue.
    // 3. Update all RUN_STAGE_WFR record. (i.e. update the status from INIT to STOP)
    // 4. Update WO_WFR records for wafer trace purposes. (i.e. location DTM, event, queue, etc.)
    Response       = False$
    If (RDSNo NE '') and (WfrID NE '') and (StageID NE '') then
        CurrDTM = Datetime()
        FormattedWfrID = WfrID
        Convert '*' to '.' in FormattedWfrID
        RunStageWfrKey = RDSNo:'*':StageID:'*':FormattedWfrID
        RunStageWfrRec = Database_Services('ReadDataRow', 'RUN_STAGE_WFR', RunStageWfrKey)
        StageStatus    = RunStageWfrRec<RUN_STAGE_WFR_STATUS$>
        If StageStatus _EQC 'START' then
            ToolQ = Gan_Services('GetWfrQueue', WfrID)
*            Gan_Services('RemoveWfrFromWIP', WfrID)
            NextStageID = Gan_Services('GetNextStage', WfrID)
            NextLocQ    = Gan_Services('GetLocQueueID', NextStageID)
*            Gan_Services('MoveWfrToQueue', WfrID, NextLocQ)                       
            InvAction      = 'STOP'
            // Sign InvAction, Fill InvAction DTM, 
            SpecInvActions = RunStageWfrRec<RUN_STAGE_WFR_SPEC_INV_ACTION$>
            SIACompBy      = RunStageWfrRec<RUN_STAGE_WFR_SIA_COMP_BY$>
            SIACompDTM     = RunStageWfrRec<RUN_STAGE_WFR_SIA_COMP_DTM$>
            CompBy         = RunStageWfrRec<RUN_STAGE_WFR_COMP_BY$>
            CompDTM        = RunStageWfrRec<RUN_STAGE_WFR_COMP_DTM$>
            Locate InvAction in SpecInvActions using @VM setting vPos then
                SIACompBy<0, vPos>  = @USER4
                SIACompDTM<0, vPos> = CurrDTM
            end
            RunStageWfrRec<RUN_STAGE_WFR_SIA_COMP_BY$>  = SIACompBy
            RunStageWfrRec<RUN_STAGE_WFR_SIA_COMP_DTM$> = SIACompDTM
            // Set the status
            RunStageWfrRec<RUN_STAGE_WFR_STATUS$> = 'STOP'
            Database_Services('WriteDataRow', 'RUN_STAGE_WFR', RunStageWfrKey, RunStageWfrRec, True$, False$, True$)
           
            WOWfrRec = Database_Services('ReadDataRow', 'WO_WFR', WfrID)
            LocDTM   = WOWfrRec<WO_WFR_LOC_DTM$>
            NextPos  = DCount(LocDTM, @VM) + 1
            WOWfrRec<WO_WFR_LOC_DTM$, NextPos>   = CurrDTM
            WOWfrRec<WO_WFR_LOC_BY$, NextPos>    = @User4
            WOWfrRec<WO_WFR_LOC_EVENT$, NextPos> = 'STOP'
            WOWfrRec<WO_WFR_TOOL_ID$, NextPos>   = ''
            WOWfrRec<WO_WFR_INV_LOC$, NextPos>   = NextLocQ
            Database_Services('WriteDataRow', 'WO_WFR', WfrID, WOWfrRec, True$, False$, True$)            
            If Error_Services('NoError') then Response = True$
        end
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// CompleteWaferStage
//
//
//----------------------------------------------------------------------------------------------------------------------
Service CompleteWaferStage(RDSNo, WfrID, StageID)
    
    Response       = False$
    If (RDSNo NE '') and (WfrID NE '') and (StageID NE '') then
        CurrDTM = Datetime()
        FormattedWfrID = WfrID
        Convert '*' to '.' in FormattedWfrID
        RunStageWfrKey = RDSNo:'*':StageID:'*':FormattedWfrID
        RunStageWfrRec = Database_Services('ReadDataRow', 'RUN_STAGE_WFR', RunStageWfrKey)
        StageStatus    = RunStageWfrRec<RUN_STAGE_WFR_STATUS$>
        If StageStatus _EQC 'STOP' then
            // Mark RUN_STAGE_WFR as complete
            RunStageWfrRec<RUN_STAGE_WFR_COMP_BY$>      = @User4
            RunStageWfrRec<RUN_STAGE_WFR_COMP_DTM$>     = CurrDTM
            RunStageWfrRec<RUN_STAGE_WFR_STATUS$>       = 'COMP'
            Database_Services('WriteDataRow', 'RUN_STAGE_WFR', RunStageWfrKey, RunStageWfrRec, True$, False$, True$)
            If Error_Services('NoError') then
                Response  = True$
                DestStage = GaN_Services('IsDestStage', StageID)
                If DestStage EQ True$ then
                    // Update EA spreadsheet with all fully characterized wafers (i.e. destroyed wafers)
                    Engineering_Services('PostEARequest', RDSNo, 'CHAR')
                end
            end
        end
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// SkipWaferStage
//
//
//----------------------------------------------------------------------------------------------------------------------
Service SkipWaferStage(RDSNo, WfrID, StageID)
    
    Response = False$
    If (RDSNo NE '') and (WfrID NE '') and (StageID NE '') then
        CurrDTM     = Datetime()
        NextLocQ    = ''
        CurrStageID = Gan_Services('GetCurrStage', WfrID)
        FormattedWfrID = WfrID
        Convert '*' to '.' in FormattedWfrID
        RunStageWfrKey = RDSNo:'*':StageID:'*':FormattedWfrID
        RunStageWfrRec = Database_Services('ReadDataRow', 'RUN_STAGE_WFR', RunStageWfrKey)
        // Mark RUN_STAGE_WFR as complete                   
        RunStageWfrRec<RUN_STAGE_WFR_COMP_BY$>  = @USER4
        RunStageWfrRec<RUN_STAGE_WFR_COMP_DTM$> = CurrDTM
        // Set the wafer status
        RunStageWfrRec<RUN_STAGE_WFR_STATUS$>   = 'SKIP'
        Database_Services('WriteDataRow', 'RUN_STAGE_WFR', RunStageWfrKey, RunStageWfrRec, True$, False$, True$)
        // Are we in the same queue that we are trying to skip? If so, then need to move the wafers to the
        // next prescribed stage, otherwise, we do not.
        If CurrStageID _EQC StageID then
            LocQ        = Gan_Services('GetWfrQueue', WfrID)
            Gan_Services('RemoveWfrFromWIP', WfrID)
            NextStageID = Gan_Services('GetNextStage', WfrID)
            NextLocQ    = Gan_Services('GetLocQueueID', NextStageID)
            Gan_Services('MoveWfrToQueue', WfrID, NextLocQ)
            WOWfrRec = Database_Services('ReadDataRow', 'WO_WFR', WfrID)
            LocDTM   = WOWfrRec<WO_WFR_LOC_DTM$>
            NextPos  = DCount(LocDTM, @VM) + 1
            WOWfrRec<WO_WFR_LOC_DTM$, NextPos>   = CurrDTM
            WOWfrRec<WO_WFR_LOC_BY$, NextPos>    = @USER4
            WOWfrRec<WO_WFR_LOC_EVENT$, NextPos> = 'SKIP'
            WOWfrRec<WO_WFR_TOOL_ID$, NextPos>   = ''
            WOWfrRec<WO_WFR_INV_LOC$, NextPos>   = NextLocQ
            Database_Services('WriteDataRow', 'WO_WFR', WfrID, WOWfrRec, True$, False$, True$) 
        end
        If Error_Services('NoError') then Response = True$
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// UnskipWaferStage
//
//
//----------------------------------------------------------------------------------------------------------------------
Service UnskipWaferStage(RDSNo, WfrID, StageID)
    
    Response = False$
    If (RDSNo NE '') and (WfrID NE '') and (StageID NE '') then
        CurrDTM = Datetime()
        FormattedWfrID = WfrID
        Convert '*' to '.' in FormattedWfrID
        RunStageWfrKey = RDSNo:'*':StageID:'*':FormattedWfrID
        RunStageWfrRec = Database_Services('ReadDataRow', 'RUN_STAGE_WFR', RunStageWfrKey)
        // Mark RUN_STAGE_WFR as not complete                   
        RunStageWfrRec<RUN_STAGE_WFR_COMP_BY$>  = ''
        RunStageWfrRec<RUN_STAGE_WFR_COMP_DTM$> = ''
        // Set the wafer status
        RunStageWfrRec<RUN_STAGE_WFR_STATUS$>   = 'INIT'
        Database_Services('WriteDataRow', 'RUN_STAGE_WFR', RunStageWfrKey, RunStageWfrRec, True$, False$, True$)
        // Move wafer to the next scheduled queue
        Gan_Services('RemoveWfrFromWIP', WfrID)
        NextStageID = Gan_Services('GetNextStage', WfrID)
        NextLocQ    = Gan_Services('GetLocQueueID', NextStageID)
        Gan_Services('MoveWfrToQueue', WfrID, NextLocQ)
        WOWfrRec = Database_Services('ReadDataRow', 'WO_WFR', WfrID)
        LocDTM   = WOWfrRec<WO_WFR_LOC_DTM$>
        NextPos  = DCount(LocDTM, @VM) + 1
        WOWfrRec<WO_WFR_LOC_DTM$, NextPos>   = CurrDTM
        WOWfrRec<WO_WFR_LOC_BY$, NextPos>    = @USER4
        WOWfrRec<WO_WFR_LOC_EVENT$, NextPos> = 'UNSKIP'
        WOWfrRec<WO_WFR_TOOL_ID$, NextPos>   = ''
        WOWfrRec<WO_WFR_INV_LOC$, NextPos>   = NextLocQ
        Database_Services('WriteDataRow', 'WO_WFR', WfrID, WOWfrRec, True$, False$, True$)      
        If Error_Services('NoError') then Response = True$
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// AddWaferStage
//
//
//----------------------------------------------------------------------------------------------------------------------
Service AddWaferStage(RDSNo, WfrID, StageID, ToolID)
    
    Response = False$
    If (RDSNo NE '') and (WfrID NE '') and (StageID NE '') and (ToolID NE '') and (ToolID _NEC 'Select') then
        FormattedWfrID = WfrID
        Swap '*' with '.' in FormattedWfrID
        RunStageWfrKey = RDSNo:'*':StageID:'*':FormattedWfrID
        Gan_Services('CreateRunStageWfr', RDSNo, StageID, WfrID)
        RunStageWfrRec = Database_Services('ReadDataRow', 'RUN_STAGE_WFR', RunStageWfrKey)
        RunStageWfrRec<RUN_STAGE_WFR_TOOL_ID$> = ToolID
        Database_Services('WriteDataRow', 'RUN_STAGE_WFR', RunStageWfrKey, RunStageWfrRec, True$, False$, True$)
        If Error_Services('NoError') then Response = True$
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// RemoveWaferStage
//
//
//----------------------------------------------------------------------------------------------------------------------
Service RemoveWaferStage(RDSNo, WfrID, StageID)
    
    Response = False$
    If (RDSNo NE '') and (WfrID NE '') and (StageID NE '') then
        ReactRunRec     = Database_Services('ReadDataRow', 'REACT_RUN', RDSNo)
        WfrStages       = ReactRunRec<REACT_RUN_WFR_STAGE$>
        UnRxStages      = ReactRunRec<REACT_RUN_UNPRESCRIBED_WFR_STAGE$>
        // Remove the stage from the REACT_RUN stage list
        Locate StageID in WfrStages using @VM setting vPos then
            WfrStages  = Delete(WfrStages, 0, vPos, 0)
            UnRxStages = Delete(UnRxStages, 0, vPos, 0)
            ReactRunRec<REACT_RUN_WFR_STAGE$> = WfrStages
            ReactRunRec<REACT_RUN_UNPRESCRIBED_WFR_STAGE$> = UnRxStages
            Database_Services('WriteDataRow', 'REACT_RUN', RDSNo, ReactRunRec, True$, False$, True$)
        end                
        FormattedWfrID = WfrID
        Swap '*' with '.' in FormattedWfrID
        RunStageWfrKey = RDSNo:'*':StageID:'*':FormattedWfrID
        // Are we in the same queue that we are trying to remove? If so, then need to move the wafers to the
        // next prescribed stage, otherwise, we do not.
        CurrStageID = Gan_Services('GetCurrStage', WfrID)
        NextStageID = Gan_Services('GetNextStage', WfrID)
        NextLocQ    = Gan_Services('GetLocQueueID', NextStageID)
        If CurrStageID _EQC StageID then
            LocQ        = Gan_Services('GetWfrQueue', WfrID)
            Gan_Services('RemoveWfrFromWIP', WfrID)                                       
            Gan_Services('MoveWfrToQueue', WfrID, NextLocQ)
        end
        // Update the WO_WFR record with the LOC_BY, LOC_DTM, INV_LOC (the next prescribed location queue),
        // and LOC_EVENT (DISPOSITION)
        WOWfrKey = WfrID
        WOWfrRec = Database_Services('ReadDataRow', 'WO_WFR', WOWfrKey)
        LocDTMs  = WOWfrRec<WO_WFR_LOC_DTM$>
        NextStep = DCount(LocDTMs, @VM) + 1
        WOWfrRec<WO_WFR_LOC_DTM$, NextStep>     = Datetime()
        WOWfrRec<WO_WFR_LOC_BY$, NextStep>      = @USER4
        WOWfrRec<WO_WFR_LOC_EVENT$, NextStep>   = 'REMOVE'
        WOWfrRec<WO_WFR_TOOL_ID$, NextStep>     = ''
        WOWfrRec<WO_WFR_INV_LOC$, NextStep>     = NextLocQ
        Database_Services('WriteDataRow', 'WO_WFR', WOWfrKey, WOWfrRec, True$, False$, True$)
        ReactRunRec = Database_Services('ReadDataRow', 'REACT_RUN', RDSNo)
        // Delete the RUN_STAGE_WFR record
        If RowExists('RUN_STAGE_WFR', RunStageWfrKey) then
            Database_Services('DeleteDataRow', 'RUN_STAGE_WFR', RunStageWfrKey, True$, False$)
        end
        If Error_Services('NoError') then Response = True$
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// CharacterizeWafer
//
//
//----------------------------------------------------------------------------------------------------------------------
Service CharacterizeWafer(RDSNo, WfrID, Auto, PartialChar)

    Response = False$
    If (RDSNo NE '') and (WfrID NE '') then
        If PartialChar NE True$ then PartialChar = False$
        InWfrIDs = Xlate('REACT_RUN', RDSNo, 'IN_WFR_ID', 'X')
        Locate WfrID in InWfrIDs using @VM setting WfrIndex then
            PSNo           = Xlate('RDS', RDSNo, 'PROD_SPEC_ID', 'X')
            ANKO           = Xlate('PROD_SPEC', PSNo, 'ANKO', 'X')
            ANKOMetPockets = Xlate('PROD_SPEC', PSNo, 'ANKO_MET_POCKETS', 'X')
            ANKOMetPocket  = ANKOMetPockets<0, WfrIndex>
            If Auto NE '' then
                User = 'AUTO'
            end else
                User = @User4
            end
            ReadyToSplit = Gan_Services('ReadyToSplit', WfrID)
            If ReadyToSplit EQ True$ then
                CurrDTM = Datetime()
                // Create remaining prescribed RUN_STAGE_WFR records for the selected wafer.
                If PartialChar EQ True$ then
                    Null 
                end else
                    Gan_Services('CreateAllRunStageWfr', RDSNo, '', WfrID)
                end
                // If ANKO create XRD and PR RUN_STAGE_WFR records.
                If ANKOMetPocket EQ True$ then
                    GaN_Services('CreateRunStageWfr', RDSNo, 'JV_XRD', WfrID)
                    GaN_Services('CreateRunStageWfr', RDSNo, 'PR', WfrID)
                end
                // Update RUN_STAGE_WFR record (Set Status to 'COMP', Fill in CompBy and CompDTM)
                FormatWfrID = WfrID
                Convert '*' to '.' in FormatWfrID
                RunStageWfrKey = RDSNo:'*SPLIT*':FormatWfrID
                RunStageWfrRec = Database_Services('ReadDataRow', 'RUN_STAGE_WFR', RunStageWfrKey)
                RunStageWfrRec<RUN_STAGE_WFR_COMP_BY$>  = User
                RunStageWfrRec<RUN_STAGE_WFR_COMP_DTM$> = CurrDTM
                RunStageWfrRec<RUN_STAGE_WFR_STATUS$>   = 'CHAR'
                Database_Services('WriteDataRow', 'RUN_STAGE_WFR', RunStageWfrKey, RunStageWfrRec, True$, False$, True$)
                // Update the WO_WFR record with the LOC_BY, LOC_DTM, INV_LOC (the next prescribed location queue),
                // and LOC_EVENT (CHARACTERIZE)
                WOWfrKey = WfrID
                WOWfrRec = Database_Services('ReadDataRow', 'WO_WFR', WOWfrKey)
                LocDTMs  = WOWfrRec<WO_WFR_LOC_DTM$>
                NextStep = DCount(LocDTMs, @VM) + 1
                WOWfrRec<WO_WFR_LOC_DTM$, NextStep>     = CurrDTM
                WOWfrRec<WO_WFR_LOC_BY$, NextStep>      = User
                WOWfrRec<WO_WFR_LOC_EVENT$, NextStep>   = 'CHARACTERIZE'
                WOWfrRec<WO_WFR_TOOL_ID$, NextStep>     = ''
                CurrStage = Gan_Services('GetCurrStage', WfrID)
                CurrLocQ  = Gan_Services('GetLocQueueID', CurrStage)
                NextStage = Gan_Services('GetNextStage', WfrID)
                NextLocQ  = Gan_Services('GetLocQueueID', NextStage)
                WoWfrRec<WO_WFR_INV_LOC$, NextStep> = NextLocQ
                // Move wafer to the next prescribed location queue
                Gan_Services('RemoveWfrFromWIP', WfrID)
                Gan_Services('MoveWfrToQueue', WfrID, NextLocQ)
                Database_Services('WriteDataRow', 'WO_WFR', WOWfrKey, WOWfrRec, True$, False$, True$)
                If Error_Services('NoError') then
                    Response = True$
                    // Mark the wafer as characterized in the REACT_RUN record.
                    ReactRunRec = Database_Services('ReadDataRow', 'REACT_RUN', RDSNo)
                    InWfrIDs    = ReactRunRec<REACT_RUN_IN_WFR_ID$>
                    Locate WfrID in InWfrIDs using @VM setting wPos then
                        ReactRunRec<REACT_RUN_CHAR_WFR_FLAG$, wPos> = True$
                        // Add this wafer ID to the list of characterized wafers so that we can
                        // determine primary and secondary characterization.
                        Locate WfrID in ReactRunRec<REACT_RUN_CHAR_LIST$> using @VM setting dummy else
                            ReactRunRec<REACT_RUN_CHAR_LIST$, -1> = WfrID
                            Database_Services('WriteDataRow', 'REACT_RUN', RDSNo, ReactRunRec, True$, False$, True$)
                        end                                       
                    end
                    If PartialChar NE True$ then
                        // Record the run number in the work order so that we can calculate when
                        // the next characterization should take place.
                        WONo     = Field(WfrID, '*', 1)
                        WOLogRec = Database_Services('ReadDataRow', 'WO_LOG', WONo)
                        RunNo    = ReactRunRec<REACT_RUN_CASS_NO$>
                        LastCharRun = WOLogRec<WO_LOG_LAST_CHAR_RUN$>[-1, 'B':@VM]
                        If ( (LastCharRun EQ '') or (RunNo GT LastCharRun) ) then
                            LastCharRun = RunNo
                            WOLogRec<WO_LOG_LAST_CHAR_RUN$, -1> = LastCharRun
                            Database_Services('WriteDataRow', 'WO_LOG', WONo, WOLogRec, True$, False$, True$)
                        end
                    end
                end
            end else
                ErrorMsg = 'This wafer is not ready to split. One or more previous stages have not been completed.'
                Error_Services('Add', ErrorMsg)
            end    
        end
    end
    
end service


Service CharEquation(RDSNo)
    
    return 0
    
end service


//----------------------------------------------------------------------------------------------------------------------
// EligibleToWithdrawWfr
//
// Input:
//      RDSNo       - [Required]
//      WfrID       - [Required]
//
// Output:
//      Response - True$ (1) if eligible, False$ (0) otherwise
//
//----------------------------------------------------------------------------------------------------------------------
Service EligibleToWithdrawWfr(RDSNo, WfrID)
    
    Response    = True$
    If (RDSNo NE '') and (WfrID NE '') then
        WfrStages   = Xlate('REACT_RUN', RDSNo, 'WFR_STAGE', 'X')
        Response    = True$
        CharDue     = False$
        Locate 'SPLIT' in WfrStages using @VM setting StartPos then
            // Was the wafer characterized or dispositioned?
            FormattedWfrID = WfrID
            Convert '*' to '.' in FormattedWfrID
            SplitKey = RDSNo:'*SPLIT*':FormattedWfrID
            SplitStatus = Xlate('RUN_STAGE_WFR', SplitKey, 'STATUS', 'X')
            
            // Check to see if characterization is due for this wafer.
            // If so, this wafer is not eligble to be withdrawn.
            WONo         = Field(WfrID, '*', 1)
            WOLogRec     = Database_Services('ReadDataRow', 'WO_LOG', WONo)
            PrevCharRuns = WOLogRec<WO_LOG_LAST_CHAR_RUN$>
            NumCharRuns  = DCount(PrevCharRuns, @VM)
            LastCharRun  = PrevCharRuns<0, NumCharRuns>
            If LastCharRun EQ '' then LastCharRun = 0
            ReactRunRec  = Database_Services('ReadDataRow', 'REACT_RUN', RDSNo)
            InWfrIDs     = ReactRunRec<REACT_RUN_IN_WFR_ID$>
            LastWafer    = DCount(InWfrIDs, @VM)
            RunNo        = ReactRunRec<REACT_RUN_CASS_NO$>
            PSNo         = Xlate('REACT_RUN', RDSNo, 'PS_NO', 'X')
            CharFreq     = Xlate('PROD_SPEC', PSNo, 'CHAR_FREQ', 'X')                             
            If ( (RunNo - LastCharRun) EQ CharFreq ) then
                // Characterization is due for this run
                SelectedWafer = GaN_Services('CharEquation', RDSNo)
                If SelectedWafer EQ 0 then SelectedWafer = LastWafer
                If SelectedWafer EQ WfrID then
                    // Characterization is due for this wafer
                    CharDue = True$
                end
            end
            
            Begin Case
                
                Case CharDue EQ True$
                    Response = False$
                    
                Case SplitStatus _EQC 'CHAR'
                    // Wafer was characterized. Ensure no metrology stages have started or been completed.
                    NextStage = StartPos + 1
                    NumStages = DCount(WfrStages, @VM)
                    For StageIndex = NextStage to NumStages
                        Stage = WfrStages<0, StageIndex>
                        RunStageWfrKey = RDSNo:'*':Stage:'*':FormattedWfrID
                        StageStatus = Xlate('RUN_STAGE_WFR', RunStageWfrKey, 'STATUS', 'X')
                        If StageStatus _EQC 'START' or StageStatus _EQC 'COMP' then
                            Response = False$
                        end
                    Until Response EQ False$
                    Next StageIndex
                    
                Case SplitStatus _EQC 'DISP'
                    // Wafer was dispositioned. Ensure G_PACK and RETAIN have not been completed.
                    GPackKey     = RDSNo:'*G_PACK*':FormattedWfrID
                    GPackStatus  = Xlate('RUN_STAGE_WFR', GPackKey, 'STATUS', 'X')
                    RetainSig    = Xlate('WO_WFR', WfrID, 'RETAIN_SIG', 'X')
                    If ( (GPackStatus _EQC 'COMP') or (RetainSig NE '') ) then
                        Response = False$
                    end
                    
            End Case            
        end           
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// WithdrawWfr
//
// Input:
//      RDSNo       - [Required]
//      WfrID       - [Required]
//
// Output:
//      Response - True$ (1) if successful, False$ (0) otherwise
//
//----------------------------------------------------------------------------------------------------------------------
Service WithdrawWfr(RDSNo, WfrID)
    CurrDTM = DateTime()
    Response = False$
    If (RDSNo NE '') and (WfrID NE '') then
        WfrStages      = Xlate('REACT_RUN', RDSNo, 'WFR_STAGE', 'X')
        FormattedWfrID = WfrID
        Convert '*' to '.' in FormattedWfrID
        Locate 'SPLIT' in WfrStages using @VM setting StartPos then
            NumStages = DCount(WfrStages, @VM)
            For StageIndex = StartPos to NumStages
                Stage = WfrStages<0, StageIndex>
                FormattedWfrID = WfrID
                Convert '*' to '.' in FormattedWfrID
                RunStageWfrKey = RDSNo:'*':Stage:'*':FormattedWfrID
                If Stage _EQC 'SPLIT' then
                    ReactRunRec = Database_Services('ReadDataRow', 'REACT_RUN', RDSNo)
                    OrigReactRunRec = ReactRunRec
                    // Set RUN_STAGE_WFR record back to INIT status
                    RunStageWfrRec = Database_Services('ReadDataRow', 'RUN_STAGE_WFR', RunStageWfrKey)
                    RunStageWfrRec<RUN_STAGE_WFR_COMP_BY$>  = ''
                    RunStageWfrRec<RUN_STAGE_WFR_COMP_DTM$> = ''
                    RunStageWfrRec<RUN_STAGE_WFR_STATUS$>   = 'INIT'
                    Database_Services('WriteDataRow','RUN_STAGE_WFR',RunStageWfrKey,RunStageWfrRec,True$,False$,True$)
                    CurrStage   = Gan_Services('GetCurrStage', WfrID)
                    CurrLocQ    = Gan_Services('GetLocQueueID', CurrStage)
                    ReturnStage = 'SPLIT'
                    NextLocQ    = Gan_Services('GetLocQueueID', ReturnStage)
                    Gan_Services('RemoveWfrFromWIP', WfrID)
                    Gan_Services('MoveWfrToQueue', WfrID, NextLocQ)                   
                    // Update the WO_WFR record with the LOC_BY, LOC_DTM, INV_LOC (the next prescribed location queue),
                    // and LOC_EVENT (DISPOSITION)
                    WOWfrKey = WfrID
                    WOWfrRec = Database_Services('ReadDataRow', 'WO_WFR', WOWfrKey)
                    LocDTMs  = WOWfrRec<WO_WFR_LOC_DTM$>
                    NextStep = DCount(LocDTMs, @VM) + 1
                    WOWfrRec<WO_WFR_LOC_DTM$, NextStep>     = CurrDTM
                    WOWfrRec<WO_WFR_LOC_BY$, NextStep>      = @User4
                    WOWfrRec<WO_WFR_LOC_EVENT$, NextStep>   = 'WITHDRAW'
                    WOWfrRec<WO_WFR_TOOL_ID$, NextStep>     = ''
                    WOWfrRec<WO_WFR_INV_LOC$, NextStep>     = NextLocQ
                    Database_Services('WriteDataRow', 'WO_WFR', WOWfrKey, WOWfrRec, True$, False$, True$)                    
                    // Mark the wafer as NOT characterized in the REACT_RUN record.                   
                    InWfrIDs    = ReactRunRec<REACT_RUN_IN_WFR_ID$>
                    CharList    = ReactRunRec<REACT_RUN_CHAR_LIST$>
                    Locate WfrID in InWfrIDs using @VM setting wPos then
                        ReactRunRec<REACT_RUN_CHAR_WFR_FLAG$, wPos> = False$                              
                    end
                    Locate WfrID in CharList using @VM setting cPos then
                        CharList = Delete(CharList, 0, cPos, 0)
                        ReactRunRec<REACT_RUN_CHAR_LIST$> = CharList
                    end
                    If OrigReactRunRec NE ReactRunRec then
                        Database_Services('WriteDataRow', 'REACT_RUN', RDSNo, ReactRunRec, True$, False$, True$)
                    end
                    // Locate this run in the  in the WO_LOG record and remove it
                    WONo         = Field(WfrID, '*', 1)
                    WOLogRec     = Database_Services('ReadDataRow', 'WO_LOG', WONo)
                    RunNo        = ReactRunRec<REACT_RUN_CASS_NO$>
                    PrevCharRuns = WOLogRec<WO_LOG_LAST_CHAR_RUN$>
                    Locate RunNo in PrevCharRuns using @VM setting vPos then
                        // Only remove this run number if no other wafers are characterizated in this run
                        WfrCharFlags = ReactRunRec<REACT_RUN_CHAR_WFR_FLAG$>
                        TotalCharWfrs = Sum(WfrCharFlags)
                        If TotalCharWfrs EQ 0 then
                            PrevCharRuns = Delete(PrevCharRuns, 0, vPos, 0)
                            WOLogRec<WO_LOG_LAST_CHAR_RUN$> = PrevCharRuns
                            Database_Services('WriteDataRow', 'WO_LOG', WONo, WOLogRec, True$, False$, True$)
                        end
                    end
                end else
                    // Remove subsequent stages                
                    If RowExists('RUN_STAGE_WFR', RunStageWfrKey) then
                        Database_Services('DeleteDataRow', 'RUN_STAGE_WFR', RunStageWfrKey, True$, False$)
                    end
                end                
            Next StageIndex
        end                       
    end
    If Error_Services('NoError') then Response = True$
    
end service


//----------------------------------------------------------------------------------------------------------------------
// GetWfrQueue
//
// Input:
//      WfrID    - [Required]
//
// Output:
//      Response - LOCATION or TOOL_WFR queue ID
//
//----------------------------------------------------------------------------------------------------------------------
Service GetWfrQueue(WfrID)

    Done      = False$
    WfrQ      = ''
    GaNStages = Database_Services('ReadDataRow', 'APP_INFO', 'GAN_STAGES')
    GaNStages = SRP_Array('Rotate', GaNStages, @FM, @VM)
    // Need to scan all LOCATION queues for the wafer.
    LocQs       = GaNStages<6>
    ToolClasses = GaNStages<3>
    For each LocQKey in LocQs using @VM setting vPos
        If LocQKey NE '' then
            LocQRec = Database_Services('ReadDataRow', 'LOCATION', LocQKey)
            WfrsInQ = LocQRec<LOCATION_WFR_ID$>
            Locate WfrID in WfrsInQ using @VM setting wPos then
                Done = True$
                WfrQ = LocQKey
            end
        end
    Until Done EQ True$
    Next LocQRec
    If Done EQ False$ then
        // Wafer not yet found. Scan all TOOL_WFR queues for the wafer.
        // Get tool class, then look up associated tool IDs.
        For each ToolClassID in ToolClasses using @VM setting vPos
            If ToolClassID NE '' then
                ToolIDs = Xlate('TOOL_CLASS', ToolClassID, 'TOOL', 'X')
                For each ToolID in ToolIDs using @VM setting tPos
                    If (ToolID NE '') and (RowExists('TOOL_WFR', ToolID)) then
                        ToolQRec = Database_Services('ReadDataRow', 'TOOL_WFR', ToolID)
                        WfrsInQ  = ToolQRec<TOOL_WFR_WFR_ID$>
                        Locate WfrID in WfrsInQ using @VM setting wPos then
                            Done = True$
                            WfrQ = ToolID
                        end
                    end
                Until Done EQ True$
                Next ToolID               
            end
        Until Done EQ True$
        Next ToolClassID        
    end
    
    Response   = WfrQ  
    
end service


//----------------------------------------------------------------------------------------------------------------------
// GetEtchQueue
//
// Input:
//      EtchID    - [Required]
//
// Output:
//      Response - LOCATION or TOOL_WFR queue ID
//
//----------------------------------------------------------------------------------------------------------------------
Service GetEtchQueue(EtchID)

    Done        = False$
    EtchQ       = ''
    GaNStages   = Database_Services('ReadDataRow', 'APP_INFO', 'GAN_STAGES')
    GaNStages   = SRP_Array('Rotate', GaNStages, @FM, @VM)
    // Need to scan all LOCATION queues for the etch.
    LocQs       = GaNStages<6>
    ToolClasses = GaNStages<3>
    For each LocQKey in LocQs using @VM setting vPos
        If LocQKey NE '' then
            LocQRec  = Database_Services('ReadDataRow', 'LOCATION', LocQKey)
            ItemsInQ = LocQRec<LOCATION_ETCH_ID$>
            Locate EtchID in ItemsInQ using @VM setting wPos then
                Done  = True$
                EtchQ = LocQKey
            end
        end
    Until Done EQ True$
    Next LocQRec
    If Done EQ False$ then
        // Etch not yet found. Scan all TOOL_WFR queues for the wafer.
        // Get tool class, then look up associated tool IDs.
        For each ToolClassID in ToolClasses using @VM setting vPos
            If ToolClassID NE '' then
                ToolIDs = Xlate('TOOL_CLASS', ToolClassID, 'TOOL', 'X')
                For each ToolID in ToolIDs using @VM setting tPos
                    If (ToolID NE '') and (RowExists('TOOL_ETCH', ToolID)) then
                        ToolQRec = Database_Services('ReadDataRow', 'TOOL_ETCH', ToolID)
                        ItemsInQ = ToolQRec<TOOL_ETCH.ETCH_ID$>
                        Locate EtchID in ItemsInQ using @VM setting wPos then
                            Done  = True$
                            EtchQ = ToolID
                        end
                    end
                Until Done EQ True$
                Next ToolID               
            end
        Until Done EQ True$
        Next ToolClassID        
    end
    
    Response = EtchQ
    
end service


//----------------------------------------------------------------------------------------------------------------------
// GetToolClassIDs
//
// Input:
//      StageID  - [Required]
//
// Output:
//      Response - Tool class associated with the given stage ID.
//
//----------------------------------------------------------------------------------------------------------------------
Service GetToolClassIDs(StageID)
    
    ToolClasses = ''
    If StageID NE '' then
        GaNStageInfo   = Database_Services('ReadDataRow', 'APP_INFO', 'GAN_STAGES')
        GaNStageInfo   = SRP_Array('Rotate', GaNStageInfo, @FM, @VM)
        StageIDs       = GaNStageInfo<1>
        AllToolClasses = GaNStageInfo<3>
        Locate StageID in StageIDs using @VM setting vPos then
            ToolClasses = AllToolClasses<0, vPos>
        end
    end
    Response  = ToolClasses
    
end service


//----------------------------------------------------------------------------------------------------------------------
// GetToolIDs
//
// Input:
//      StageID  - [Required]
//      ToolMode - [Optional]
//
// Output:
//      Response - Tool IDs associated with the given stage ID.
//
//----------------------------------------------------------------------------------------------------------------------
Service GetToolIDs(StageID, ToolMode)

    ToolIDs = ''
    If StageID NE '' then
        ToolClassIDs = Gan_Services('GetToolClassIDs', StageID)
        If ToolClassIDs NE '' then
            For each ToolClassID in ToolClassIDs using @SVM setting svPos
                TempArray = Xlate('TOOL_CLASS', ToolClassID, 'TOOL', 'X')
                If ToolMode NE '' then
                    // Filter on ToolMode
                    FilteredArray = ''
                    For each ToolID in TempArray using @VM
                        CurrMode = Xlate('TOOL', ToolID, 'CURR_MODE', 'X')
                        If CurrMode EQ ToolMode then
                            FilteredArray<0, -1> = ToolID
                        end                        
                    Next ToolID
                    TempArray = FilteredArray
                end                       
                ToolIDs   = SRP_Array('Join', ToolIDs, TempArray, 'OR', @VM)                               
            Next ToolClassID
            If ToolIDs[-1, 1] EQ @VM then ToolIDs[-1, 1] = '' 
        end
    end
    
    Response  = ToolIDs
    
end service


//----------------------------------------------------------------------------------------------------------------------
// IsDestStage
//
// Input:
//      StageID  - [Required]
//
// Output:
//      Response - True$ (1) if stage is destructive, False$ (0) otherwise.
//
//----------------------------------------------------------------------------------------------------------------------
Service IsDestStage(StageID)
    
    Destructive  = False$
    ToolClassIDs = Gan_Services('GetToolClassIDs', StageID)  
    For each ToolClassID in ToolClassIDs using @VM setting vPos
        DestClass = Xlate('TOOL_CLASS', ToolClassID, 'DEST_TEST', 'X')
        If DestClass EQ True$ then Destructive = True$
    Until Destructive EQ True$
    Next ToolClassID
    Response  = Destructive
    
end service


//----------------------------------------------------------------------------------------------------------------------
// ReadyToSplit
//
// Input:
//      WfrID    - [Required]
//
// Output:
//      Response - True$ (1) if WfrID is ready for characterization (i.e. SPLIT) and/or disposition (i.e. DISP),
//                 False$ (0) otherwise.
//
//----------------------------------------------------------------------------------------------------------------------
Service ReadyToSplit(WfrID)
    
    Response = 1
end service 
    Done         = False$
    ReadyToSplit = True$
    If WfrID NE '' then
        // Convert delimiters in case the key is passed in with the wrong format
        Convert '.' to '*' in WfrID
        GaNStageInfo = Database_Services('ReadDataRow', 'APP_INFO', 'GAN_STAGES')
        GaNStageInfo = SRP_Array('Rotate', GaNStageInfo, @FM, @VM)
        StageIDs     = GaNStageInfo<1>
        StageTypes   = GaNStageInfo<5>
        // 1. Check that all reactor stages (RUN_STAGE) are complete
        // 2. Check that all prescribed metrology stages up to, but not including, SPLIT are complete
        ReactRunNo   = Xlate('WO_WFR', WfrID, 'RDS_NO', 'X')
        ReactRunRec  = Database_Services('ReadDataRow', 'REACT_RUN', ReactRunNo)
        RunStages    = ReactRunRec<REACT_RUN_RUN_STAGE_KEY$>
        WfrStages    = ReactRunRec<REACT_RUN_WFR_STAGE$>
        UnRxStages   = ReactRunRec<REACT_RUN_UNPRESCRIBED_WFR_STAGE$>
        For each RunStageKey in RunStages using @VM setting vPos
            StageID = RunStageKey[-1, 'B*']
            Locate StageID in StageIDs using @VM setting vPos then
                StageType = StageTypes<0, vPos>
                If StageType _EQC 'REACTOR' then
                    CompBy = Xlate('RUN_STAGE', RunStageKey, 'COMP_BY', 'X')
                    If CompBy EQ '' then ReadyToSplit = False$
                end
            end            
        Until ReadyToSplit EQ False$
        Next RunStageKey
        If ReadyToSplit EQ True$ then
            For each WfrStage in WfrStages using @VM setting vPos
            Until WfrStage _EQC 'SPLIT'
                Skip = False$
                StageUnRx = UnRxStages<0, vPos>
                If StageUnRx EQ True$ then Skip = True$
                If Skip EQ False$ then
                    Convert '*' to '.' in WfrID
                    RunStageWfrKey = ReactRunNo:'*':WfrStage:'*':WfrID
                    CompBy = Xlate('RUN_STAGE_WFR', RunStageWfrKey, 'COMP_BY', 'X')
                    If CompBy EQ '' then ReadyToSplit = False$
                end
            Until ReadyToSplit EQ False$
            Next WfrStage
        end
    end
    Response  = ReadyToSplit
    
end service


//----------------------------------------------------------------------------------------------------------------------
// ReadyToSplit
//
// Input:
//      WfrID    - [Required]
//
// Output:
//      Response - True$ (1) if any WfrID has been characterized or dispositioned, False$ (0) otherwise.
//
//----------------------------------------------------------------------------------------------------------------------
Service IsCassSplit(RDSNo)
    
    Done      = False$
    CassSplit = False$
    If RDSNo NE '' then
        WfrIDs = Xlate('REACT_RUN', RDSNo, 'IN_WFR_ID', 'X')
        Stage  = 'SPLIT'
        For each WfrID in WfrIDs using @VM setting vPos
            Convert '*' to '.' in WfrID
            RunStageWfrKey  = RDSNo:'*':Stage:'*':WfrID
            SplitStatus     = Xlate('RUN_STAGE_WFR', RunStageWfrKey, 'STATUS', 'X')
            If SplitStatus _EQC 'CHAR' or SplitStatus _EQC 'DISP' then
                CassSplit = True$
                Done      = True$
            end
        Until Done EQ True$
        Next WfrID        
    end
    Response  = CassSplit
    
end service


//----------------------------------------------------------------------------------------------------------------------
// GetRunSplitStatus
//
// Input:
//      RDSNo    - [Required]
//
// Output:
//      Response - True$ (1) if all wafers in the run have been characterized or dispositioned, False$ (0) otherwise.
//
//----------------------------------------------------------------------------------------------------------------------
Service GetRunSplitStatus(RDSNo)
    
    SplitComp = True$
    If RDSNo NE '' then
        WfrIDs = Xlate('REACT_RUN', RDSNo, 'IN_WFR_ID', 'X')
        Stage  = 'SPLIT'
        For each WfrID in WfrIDs using @VM setting vPos
            Convert '*' to '.' in WfrID
            RunStageWfrKey  = RDSNo:'*':Stage:'*':WfrID
            SplitStatus     = Xlate('RUN_STAGE_WFR', RunStageWfrKey, 'STATUS', 'X')
            If SplitStatus _EQC 'CHAR' or SplitStatus _EQC 'DISP' then
                Null
            end else
                SplitComp = False$
            end
        Until SplitComp EQ False$
        Next WfrID        
    end
    Response = SplitComp
    
end service


//----------------------------------------------------------------------------------------------------------------------
// ReadyToStart
//
// Input:
//      WfrID    - [Required]
//
// Output:
//      Response - True$ (1) if any WfrID is ready to start a stage (i.e. it has not already started on another
//                 stage, False$ (0) otherwise.
//
//----------------------------------------------------------------------------------------------------------------------
Service ReadyToStart(RDSNo, WfrID)
    
    Done         = False$
    ReadyToStart = True$
    Convert '*' to '.' in WfrID
    If WfrID NE '' then
        
        WfrStages = Xlate('REACT_RUN', RDSNo, 'WFR_STAGE', 'X')
        For each Stage in WfrStages using @VM setting vPos
            RunStageWfrKey = RDSNo:'*':Stage:'*':WfrID
            StageStatus    = Xlate('RUN_STAGE_WFR', RunStageWfrKey, 'STATUS', 'X')
            If StageStatus _EQC 'START' then
                ReadyToStart = False$
                Done         = True$
            end
        Until Done EQ True$
        Next Stage
    end
    Response  = ReadyToStart
    
end service


//----------------------------------------------------------------------------------------------------------------------
// WfrIsRunning
//
// Input:
//      WfrID    - [Required]
//
// Output:
//      Response - True$ (1) if any WfrID is running on a tool, False$ (0) otherwise.
//
//----------------------------------------------------------------------------------------------------------------------
Service WfrIsRunning(WfrID)

    WfrIsRunning = False$
    Convert '.' to '*' in WfrID
    RDSNo        = Xlate('WO_WFR', WfrID, 'RDS_NO', 'X')
    Convert '*' to '.' in WfrID
    If WfrID NE '' then
        WfrStages = Xlate('REACT_RUN', RDSNo, 'WFR_STAGE', 'X')
        For each Stage in WfrStages using @VM setting vPos
            RunStageWfrKey = RDSNo:'*':Stage:'*':WfrID
            StageStatus    = Xlate('RUN_STAGE_WFR', RunStageWfrKey, 'STATUS', 'X')
            If StageStatus _EQC 'START' then
                WfrIsRunning = True$
            end
        Until WfrIsRunning EQ True$
        Next Stage
    end
    Response = WfrIsRunning
    
end service


//----------------------------------------------------------------------------------------------------------------------
// GetWfrRunTool
//
// Input:
//      WfrID    - [Required]
//
// Output:
//      Response - True$ (1) if any WfrID is running on a tool, False$ (0) otherwise.
//
//----------------------------------------------------------------------------------------------------------------------
Service GetWfrRunTool(WfrID)

    ToolID = ''
    Convert '.' to '*' in WfrID
    RDSNo  = Xlate('WO_WFR', WfrID, 'RDS_NO', 'X')
    Convert '*' to '.' in WfrID
    If WfrID NE '' then
        WfrStages = Xlate('REACT_RUN', RDSNo, 'WFR_STAGE', 'X')
        For each Stage in WfrStages using @VM setting vPos
            RunStageWfrKey = RDSNo:'*':Stage:'*':WfrID
            StageStatus    = Xlate('RUN_STAGE_WFR', RunStageWfrKey, 'STATUS', 'X')
            If StageStatus _EQC 'START' then
                ToolID = Xlate('RUN_STAGE_WFR', RunStageWfrKey, 'TOOL_ID', 'X')
            end
        Until ToolID NE ''
        Next Stage
    end
    Response = ToolID
    
end service


//----------------------------------------------------------------------------------------------------------------------
// GetUnRxStatus
//
// Input:
//      RDSNo    - [Required]
//      StageID  - [Required]
//
// Output:
//      Response - True$ (1) if StageID is unprescribed by the PSN, False$ (0) otherwise.
//
//----------------------------------------------------------------------------------------------------------------------
Service GetUnRxStatus(RDSNo, StageID)
    
    UnRxStage = False$
    If (RDSNo NE '') and (StageID NE '') then
        ReactRunRec = Database_Services('ReadDataRow', 'REACT_RUN', RDSNo)
        WfrStages   = ReactRunRec<REACT_RUN_WFR_STAGE$>
        UnRxStages  = ReactRunRec<REACT_RUN_UNPRESCRIBED_WFR_STAGE$>
        Locate StageID in WfrStages using @VM setting vPos then
            UnRxStage = UnRxStages<0, vPos>
            If UnRxStage EQ '' then UnRxStage = 0
        end else
            // Stage not yet added, so we know it is unprescribed.
            UnRxStage = True$
        end
    end
    Response = UnRxStage
    
end service


//----------------------------------------------------------------------------------------------------------------------
// GetMetrologyStatus
//
// Input:
//      WfrID    - [Required]
//
// Output:
//      Response - True$ (1) if all metrology stages prescribed and unprescribed have been skipped or completed,
//                 False$ (0) otherwise.
//
//----------------------------------------------------------------------------------------------------------------------
Service GetMetrologyStatus(WfrID)

    MetStatus = ''
    If (WfrID NE '') then
        FormattedWfrID = WfrID
        Convert '*' to '.' in FormattedWfrID
        RDSNo       = Xlate('WO_WFR', WfrID, 'RDS_NO', 'X')
        GRateKey    = RDSNo:'*G_RATE'
        GRateStatus = Xlate('RUN_STAGE', GRateKey, 'STATUS', 'X')
        If GRateStatus NE 'COMP' then MetStatus = False$
        If ( (RDSNo NE '') and (GRateStatus _EQC 'COMP') ) then
            ReactRunRec = Database_Services('ReadDataRow', 'REACT_RUN', RDSNo)
            WfrStages   = ReactRunRec<REACT_RUN_WFR_STAGE$>
            For each MetStage in WfrStages using @VM setting StageIndex                
            Until MetStage _EQC 'DISP'
                RunStageWfrKey = RDSNo:'*':MetStage:'*':FormattedWfrID
                If RowExists('RUN_STAGE_WFR', RunStageWfrKey) then
                    StageStatus = Xlate('RUN_STAGE_WFR', RunStageWfrKey, 'STATUS', 'X')
                    If StageStatus _EQC 'START' or StageStatus _EQC 'INIT' then                       
                        MetStatus = False$
                    end
                end
            Until MetStatus EQ False$
            Next MetStage
            If MetStatus EQ '' then MetStatus = True$
        end
    end
    Response = MetStatus
    
end service


//----------------------------------------------------------------------------------------------------------------------
// GetDispositionStatus
//
// Input:
//      WfrID    - [Required]
//
// Output:
//      Response - True$ (1) if wafer is currently at or has previously been at the DISP stage, False$ (0) otherwise.
//
//----------------------------------------------------------------------------------------------------------------------
Service GetDispStatus(WfrID)

    DispStatus = False$
    If (WfrID NE '') then
        FormattedWfrID = WfrID
        Convert '*' to '.' in FormattedWfrID
        RDSNo = Xlate('WO_WFR', WfrID, 'RDS_NO', 'X')
        If (RDSNo NE '') then
            DispStageKey = RDSNo:'*DISP*':FormattedWfrID
            If RowExists('RUN_STAGE_WFR', DispStageKey) then
                StageStatus = Xlate('RUN_STAGE_WFR', DispStageKey, 'STATUS', 'X')
                CurrLoc     = Gan_Services('GetCurrStage', WfrID)
                If ( (StageStatus _EQC 'COMP') or (CurrLoc _EQC 'DISP') ) then                 
                    DispStatus = True$
                end
            end
        end
    end
    Response = DispStatus
    
end service


//----------------------------------------------------------------------------------------------------------------------
// GetDestroyedStatus
//
// Input:
//      WfrID    - [Required]
//
// Output:
//      Response - True$ (1) if a destructive metrology stage has been completed, False$ (0) otherwise.
//
//----------------------------------------------------------------------------------------------------------------------
Service GetDestroyedStatus(WfrID)
    
    DestStatus = False$
    If (WfrID NE '') then
        FormattedWfrID = WfrID
        Convert '*' to '.' in FormattedWfrID
        RDSNo = Xlate('WO_WFR', WfrID, 'RDS_NO', 'X')
        If (RDSNo NE '') then
            ReactRunRec   = Database_Services('ReadDataRow', 'REACT_RUN', RDSNo)
            WfrIDs        = ReactRunRec<REACT_RUN_IN_WFR_ID$>
            ExternalFlags = ReactRunRec<REACT_RUN_EXTERNAL$>
            Locate WfrID in WfrIDs using @VM setting WaferPos then
                ExternalFlag = ExternalFlags<0, WaferPos>
                If ExternalFlag EQ True$ then DestStatus = True$
            end
            If DestStatus EQ False$ then
                WfrStages     = ReactRunRec<REACT_RUN_WFR_STAGE$>
                For each MetStage in WfrStages using @VM setting StageIndex                
                    If GaN_Services('IsDestStage', MetStage) then
                        RunStageWfrKey = RDSNo:'*':MetStage:'*':FormattedWfrID
                        If RowExists('RUN_STAGE_WFR', RunStageWfrKey) then
                            StageStatus = Xlate('RUN_STAGE_WFR', RunStageWfrKey, 'STATUS', 'X')
                            If StageStatus _EQC 'START' or StageStatus _EQC 'COMP' then
                                DestStatus = True$
                            end
                        end
                    end
                Until DestStatus EQ True$
                Next MetStage    
            end           
        end
    end
    Response = DestStatus
    
end service


//----------------------------------------------------------------------------------------------------------------------
// GetDestroyedWafers
//
// Input:
//      RDSNo    - [Required]
//
// Output:
//      Response - A list of wafer IDs of destroyed wafers in the cassette.
//
//----------------------------------------------------------------------------------------------------------------------
Service GetDestroyedWafers(RDSNo)
    
    DestroyedWfrs = ''
    If RDSNo NE '' then
        ReactRunRec = Database_Services('ReadDataRow', 'REACT_RUN', RDSNo)
        If Error_Services('NoError') then           
            WfrIDs        = ReactRunRec<REACT_RUN_IN_WFR_ID$>
            For each WfrID in WfrIDs using @VM setting wPos
                WfrDestroyed = GaN_Services('GetDestroyedStatus', WfrID)
                If WfrDestroyed EQ True$ then DestroyedWfrs<0, -1> = WfrID
            Next WfrID
        end
    end
    Response = DestroyedWfrs
    
end service


//----------------------------------------------------------------------------------------------------------------------
// GetGaNWIPJSON
//
//
//----------------------------------------------------------------------------------------------------------------------
Service GetGaNWIPJSON()

    JSON         = ''
    TableList    = Database_Services('ReadDataRow', 'APP_INFO', 'GAN_WIP')
    TableList    = Delete(TableList, 1, 0, 0)
    hStatusObj   = ''
    hStageArray  = ''
    hStageObject = ''
    hRDSArray    = ''
    hRDSObject   = ''
    If SRP_JSON(hStatusObj, 'NEW', 'OBJECT') then
        If SRP_JSON(hStageArray, 'NEW', 'ARRAY') then
            PreviousStage   = ''
            For Each StatusLine in TableList using @FM
                Stage   = StatusLine<0, 1>
                If Stage NE '' then
                    // New stage being introduced. Close the previous stage if one exists.
                    If PreviousStage NE '' then
                        SRP_JSON(hStageObject, 'SET', 'RDS Items', hRDSArray)
                        SRP_JSON(hRDSArray, 'RELEASE')
                        SRP_JSON(hStageArray, 'ADD', hStageObject)
                        SRP_JSON(hStageObject, 'RELEASE')
                    end
                    SRP_JSON(hStageObject, 'NEW', 'OBJECT')
                    SRP_JSON(hStageObject, 'SETVALUE', 'stage', Stage)
                    SRP_JSON(hRDSArray, 'NEW', 'ARRAY')
                    PreviousStage   = Stage
                end else
                    RDSNo   = StatusLine<0, 2>
                    If RDSNo NE '' then
                        // RDS / Wafer line being added to the current stage.
                        SRP_JSON(hRDSObject, 'NEW', 'OBJECT')
                        SRP_JSON(hRDSObject, 'SETVALUE', 'RDS No', RDSNo, 'STRING')
                        SRP_JSON(hRDSObject, 'SETVALUE', 'Run/Etch ID', StatusLine<0, 3>,  'STRING')
                        SRP_JSON(hRDSObject, 'SETVALUE', 'Part No',     StatusLine<0, 4>,  'STRING')
                        SRP_JSON(hRDSObject, 'SETVALUE', 'Wait Pri',    StatusLine<0, 5>,  'STRING')                        
                        SRP_JSON(hRDSObject, 'SETVALUE', 'Wait Wfrs',   StatusLine<0, 6>,  'STRING')
                        SRP_JSON(hRDSObject, 'SETVALUE', 'Wait Hrs',    StatusLine<0, 7>,  'STRING')
                        SRP_JSON(hRDSObject, 'SETVALUE', 'Wait Wfr ID', StatusLine<0, 8>,  'STRING')                                               
                        SRP_JSON(hRDSObject, 'SETVALUE', 'Tool ID',     StatusLine<0, 9>, 'STRING')
                        SRP_JSON(hRDSObject, 'SETVALUE', 'Run Wfrs',    StatusLine<0, 10>, 'STRING')
                        SRP_JSON(hRDSObject, 'SETVALUE', 'Run Hrs',     StatusLine<0, 11>, 'STRING')
                        SRP_JSON(hRDSObject, 'SETVALUE', 'Run Wfr ID',  StatusLine<0, 12>, 'STRING')
                        SRP_JSON(hRDSArray, 'ADD', hRDSObject)
                        SRP_JSON(hRDSObject, 'RELEASE')
                    end
                end
            Next StatusLine
            // Add the last stage object to the stage array.
            SRP_JSON(hStageObject, 'SET', 'RDS Items', hRDSArray)
            SRP_JSON(hRDSArray, 'RELEASE')
            SRP_JSON(hStageArray, 'ADD', hStageObject)
            SRP_JSON(hStageObject, 'RELEASE')
            SRP_JSON(hStatusObj, 'SET', 'stages', hStageArray)
            SRP_JSON(hStageArray, 'RELEASE')
        end
        JSON    = SRP_JSON(hStatusObj, 'STRINGIFY', 'FAST')
        SRP_JSON(hStatusObj, 'RELEASE')
    end else
        Error_Services('Add', 'Error creating the GAN WIP Status JSON object.')
    end

    Response = JSON
    
end service


//----------------------------------------------------------------------------------------------------------------------
// SelectGaNEtchRuns
//
//
//----------------------------------------------------------------------------------------------------------------------
Service SelectGaNEtchRuns(ParentWindow, GaNEtchID, Reactor)

    // Close Etch Manager if it is already open so that we can launch it in COMMIT mode.
    EtchManVis = Get_Property('NDW_INTERNAL_ETCH_MANAGER', 'VISIBLE')
    If EtchManVis EQ True$ then End_Window('NDW_INTERNAL_ETCH_MANAGER')
    Response = Dialog_Box('NDW_INTERNAL_ETCH_MANAGER', ParentWindow, 'COMMIT':@FM:GaNEtchID:@FM:Reactor)
    
end service


//----------------------------------------------------------------------------------------------------------------------
// AddWfrsToOutCass
//
// Input:
//      OutCassID    - [Required] - 
//      SelInWfrIDs  - [Required] - Inbound wafer IDs to be be added to the outbound cassette, OutCassID.
//
//----------------------------------------------------------------------------------------------------------------------
Service AddWfrsToOutCass(OutCassID, SelInWfrIDs)
    
    CurrDTM     = Datetime()      
    WOMatWfrKey = OutCassID
    WOMatWfrRec = Database_Services('ReadDataRow', 'WO_MAT_WFR', WOMatWfrKey)
    OutWfrIDs   = WOMatWfrRec<WO_MAT_WFR_OUT_WFR_ID$>
    AvailSlots  = ''
    // Count empty (i.e. available) slots to fill
    NumWfrs     = 25
    For WfrIndex = 1 to NumWfrs
        If OutWfrIDs<0, WfrIndex> EQ '' then AvailSlots<0, -1> = OutCassID:'*':WfrIndex
    Next WaferIndex
    NumAvailSlots = DCount(AvailSlots, @VM)
    NumAddWfrs    = DCount(SelInWfrIDs, @VM)
    If NumAddWfrs LE NumAvailSlots then

        FOR AddWfrIndex = 1 TO NumAddWfrs
            WfrID       = SelInWfrIDs<1, AddWfrIndex>
            RDSNo       = Xlate('WO_WFR', WfrID, 'RDS_NO', 'X')
            ReactRunRec = Database_Services('ReadDataRow', 'REACT_RUN', RDSNo)    
            // Place IN_WFR_ID into outbound cassette
            OutWfrID = AvailSlots<0, AddWfrIndex>
            OutSlot  = Field(OutWfrID, '*', 3, 1)             
            OutWfrIDs<0, OutSlot> = WfrID
            // Place wafer into REACT_RUN outbound wafer field
            LOCATE WfrID IN ReactRunRec<REACT_RUN_IN_WFR_ID$> USING @VM SETTING Pos THEN
                ReactRunRec<REACT_RUN_OUT_SLOT_ID$, Pos> = OutWfrID
            END
            // Update REACT_RUN table (RDS table)
            Database_Services('WriteDataRow', 'REACT_RUN', RDSNo, ReactRunRec, True$, False$, True$)
            // Update WO_WFR table (wafer trace table)
            WOWfrRec = Database_Services('ReadDataRow', 'WO_WFR', WfrID)
            LOCATE WfrID IN ReactRunRec<REACT_RUN_CARR_WFR_ID$> USING @VM SETTING cPos THEN
                CurrCarrSlot = ReactRunRec<REACT_RUN_CARR_SLOT_ID$, cPos>           
            END ELSE 
                CurrCarrSlot = ''
            END
            EventDTMs = WOWfrRec<WO_WFR_LOC_DTM$>
            CurrEventIndex = DCount(EventDTMs, @VM)
            WOWfrRec<WO_WFR_SLOT_ID$, CurrEventIndex> = OutWfrID
            WOWfrRec<WO_WFR_SLOT_ID$, CurrEventIndex> = CurrCarrSlot
            Database_Services('WriteDataRow', 'WO_WFR', WfrID, WOWfrRec, True$, False$, True$)
        NEXT I
        
        // Update WO_MAT_WFR table (outbound cassette table)
        WOMatWfrRec<WO_MAT_WFR_OUT_WFR_ID$> = OutWfrIDs
        Database_Services('WriteDataRow', 'WO_MAT_WFR', WOMatWfrKey, WOMatWfrRec, True$, False$, True$)
        
    end else
        ErrorMessage = 'Add wafer operation failed. ':NumAddWfrs:' wafer(s) selected, but only ':NumAvailSlots: ' ' |
                     : 'slot(s) are available.'
        Error_Services('Add', ErrorMessage)       
    end
   
end service


//----------------------------------------------------------------------------------------------------------------------
// AddWfrToOutCass
//
//
//----------------------------------------------------------------------------------------------------------------------
Service AddWfrToOutCass(WfrID)

    If WfrID NE '' then
        WONo        = Field(WfrID, '*', 1)
        RDSNo       = Xlate('WO_WFR', WfrID, 'RDS_NO', 'X')
        WORec       = Database_Services('ReadDataRow', 'WO_LOG', WONo)
        
        // Get next outbound slot
        OutSlotID   = WORec<WO_LOG_NEXT_OUT_SLOT$>
        If OutSlotID EQ '' then
            // Initialize the next out slot field
            OutSlotID = '1*1'
        end
        OutCassNo   = OutSlotID[1, 'F*']
        OutSlotNo   = OutSlotID[Col2()+1, 2]
        OutSlotKey  = WONo:'*':OutCassNo:'*':OutSlotNo
        
        // Update next outbound slot        
        MaxShipQty  = Xlate('WO_LOG', WONo, 'CUST_EPI_PART_SHIP_QTY', 'X')
        If ( (OutSlotNo) GE MaxShipQty ) then
												  
            // Is there another outbound cassette available?
            NextCassNo = OutCassNo + 1
            NextCassKey = WONo:'*':NextCassNo
            If ( RowExists('WO_MAT_WFR', NextCassKey) EQ True$ ) then
                // We have another outbound cassette
                Null
            end else
                // No more outbound cassettes available, so create one now
                * 1. Add WO_MAT key to WO_LOG
                WORec<WO_LOG_WO_MAT_KEY$, -1> = NextCassKey               
                * 2. Create WO_MAT_WFR record
                NextWOMatWfrRec = ''
                * 3. Populate WO_MAT_WFR record with outbound slots ONLY
                * (i.e. do not populate inbound slot columns as this would be equivalent to releasing new material)
                For Slot = 1 to MaxShipQty
                    NextWOMatWfrRec<WO_MAT_WFR_OUT_PREV_WFR_ID$, -1> = WONo:'*':NextCassNo:'*':Slot
                Next Slot
                Database_Services('WriteDataRow', 'WO_MAT_WFR', NextCassKey, NextWOMatWfrRec)
            end
            NextOutSlot = NextCassNo:'*':1
        end else
            NextOutSlot = OutCassNo:'*':(OutSlotNo + 1)
        end
        
        WORec<WO_LOG_NEXT_OUT_SLOT$> = NextOutSlot
        Database_Services('WriteDataRow', 'WO_LOG', WONo, WORec, True$, False$, True$)
                      
        // Update WO_MAT_WFR table (outbound cassette table)
        CurrDTM     = Datetime()
        WOMatWfrKey = WONo:'*':OutCassNo
        WOMatWfrRec = Database_Services('ReadDataRow', 'WO_MAT_WFR', WOMatWfrKey)
        OutWfrIDs   = WOMatWfrRec<WO_MAT_WFR_OUT_WFR_ID$>              
        OutWfrIDs<0, OutSlotNo> = WfrID
        WOMatWfrRec<WO_MAT_WFR_OUT_WFR_ID$> = OutWfrIDs
        Database_Services('WriteDataRow', 'WO_MAT_WFR', WOMatWfrKey, WOMatWfrRec, True$, False$, True$)
        
        // Place wafer into REACT_RUN outbound wafer field
        ReactRunRec = Database_Services('ReadDataRow', 'REACT_RUN', RDSNo)           
        LOCATE WfrID IN ReactRunRec<REACT_RUN_IN_WFR_ID$> USING @VM SETTING Pos THEN
            ReactRunRec<REACT_RUN_OUT_SLOT_ID$, Pos> = OutSlotKey
        END
        Database_Services('WriteDataRow', 'REACT_RUN', RDSNo, ReactRunRec, True$, False$, True$)
        
        // Move wafer into the G_PACK queue
        PackQ = 'GCH*Q_PACK'
        CurrQ = Gan_Services('GetWfrQueue', WfrID)
        Gan_Services('RemoveWfrFromWIP', WfrID)
        Gan_Services('MoveWfrToQueue', WfrID, PackQ)
        
        // Update WO_WFR table (wafer trace table)
        WOWfrRec = Database_Services('ReadDataRow', 'WO_WFR', WfrID)
        LOCATE WfrID IN ReactRunRec<REACT_RUN_CARR_WFR_ID$> USING @VM SETTING cPos THEN
            CurrCarrSlot = ReactRunRec<REACT_RUN_CARR_SLOT_ID$, cPos>           
        END ELSE 
            CurrCarrSlot = ''
        END
        EventDTMs = WOWfrRec<WO_WFR_LOC_DTM$>
        NextPos  = DCount(EventDTMs, @VM) + 1
        WOWfrRec<WO_WFR_SLOT_ID$, NextPos>   = OutSlotKey
        WOWfrRec<WO_WFR_CARR_SLOT$, NextPos> = CurrCarrSlot
        WOWfrRec<WO_WFR_LOC_DTM$, NextPos>   = CurrDTM
        WOWfrRec<WO_WFR_LOC_BY$, NextPos>    = @USER4
        WOWfrRec<WO_WFR_LOC_EVENT$, NextPos> = 'COMP'
        WOWfrRec<WO_WFR_TOOL_ID$, NextPos>   = ''
        WOWfrRec<WO_WFR_INV_LOC$, NextPos>   = PackQ
        Database_Services('WriteDataRow', 'WO_WFR', WfrID, WOWfrRec, True$, False$, True$)
        // Mark DISP complete
        Convert '*' to '.' in WfrID
        RunStageWfrKey = RDSNo:'*DISP*':WfrID
        RunStageWfrRec = Database_Services('ReadDataRow', 'RUN_STAGE_WFR', RunStageWfrKey)
        StageStatus    = RunStageWfrRec<RUN_STAGE_WFR_STATUS$>
        If StageStatus _EQC 'INIT' then
            // Mark DISP stage as complete
            RunStageWfrRec<RUN_STAGE_WFR_COMP_BY$>  = @USER4
            RunStageWfrRec<RUN_STAGE_WFR_COMP_DTM$> = CurrDTM
            RunStageWfrRec<RUN_STAGE_WFR_STATUS$>   = 'COMP'
            Database_Services('WriteDataRow', 'RUN_STAGE_WFR', RunStageWfrKey, RunStageWfrRec, True$, False$, True$)
        end

    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// RemoveWfrsFromOutCass
//
// Input:
//      OutCassID    - [Required] - Outbound GaN cassette ID from which SelInWfrIDs will be removed.
//      SelInWfrIDs  - [Required] - Inbound wafer IDs to be removed from the outbound cassette, OutCassID.
//
//----------------------------------------------------------------------------------------------------------------------
Service RemoveWfrsFromOutCass(OutCassID, SelInWfrIDs)
    
    CurrDTM     = Datetime()       
    WOMatWfrKey = OutCassID
    WOMatWfrRec = Database_Services('ReadDataRow', 'WO_MAT_WFR', WOMatWfrKey)
    OutWfrIDs   = WOMatWfrRec<WO_MAT_WFR_OUT_WFR_ID$>
    NumRemWfrs  = DCount(SelInWfrIDs, @VM)
    FOR RemWfrIndex = 1 TO NumRemWfrs
        WfrID       = SelInWfrIDs<1, RemWfrIndex>
        RDSNo       = Xlate('WO_WFR', WfrID, 'RDS_NO', 'X')
        ReactRunRec = Database_Services('ReadDataRow', 'REACT_RUN', RDSNo) 
        // Remove IN_WFR_ID from outbound cassette
        Locate WfrID in OutWfrIDs using @VM setting vPos then
            OutWfrIDs<0, vPos> = ''
        end
        // Remove wafer from REACT_RUN outbound wafer field
        LOCATE WfrID IN ReactRunRec<REACT_RUN_IN_WFR_ID$> USING @VM SETTING Pos THEN
            ReactRunRec<REACT_RUN_OUT_SLOT_ID$, Pos> = ''
        END
        // Update REACT_RUN table (RDS table)
        Database_Services('WriteDataRow', 'REACT_RUN', RDSNo, ReactRunRec, True$, False$, True$)
        // Update WO_WFR table (wafer trace table)
        WOWfrRec = Database_Services('ReadDataRow', 'WO_WFR', WfrID)
        EventDTMs = WOWfrRec<WO_WFR_LOC_DTM$>
        CurrEventIndex = DCount(EventDTMs, @VM)
        WOWfrRec<WO_WFR_SLOT_ID$, CurrEventIndex> = ''
        WOWfrRec<WO_WFR_SLOT_ID$, CurrEventIndex> = ''
        Database_Services('WriteDataRow', 'WO_WFR', WfrID, WOWfrRec, True$, False$, True$)
    NEXT I
    
    // Update WO_MAT_WFR table (outbound cassette table)
    WOMatWfrRec<WO_MAT_WFR_OUT_WFR_ID$> = OutWfrIDs
    Database_Services('WriteDataRow', 'WO_MAT_WFR', WOMatWfrKey, WOMatWfrRec, True$, False$, True$)
    
end service


//----------------------------------------------------------------------------------------------------------------------
// GetOutOfSpecStatus
//
//
//----------------------------------------------------------------------------------------------------------------------
Service GetOutOfSpecStatus(RDSNo, WfrID, Split)

    If ( (RDSNo NE '') and (WfrID NE '') and (Split NE '') ) then
        DataOutOfSpec = ''
        ReactRunRec   = Database_Services('ReadDataRow', 'REACT_RUN', RDSNo)
        WfrStages     = ReactRunRec<REACT_RUN_WFR_STAGE$>
        StageList     = ''
        Begin Case
            Case Split EQ 'PRE'
                For each Stage in WfrStages using @VM setting StagePos
                Until Stage EQ 'SPLIT'
                    StageList<0, -1> = Stage
                Next Stage                
                
            Case Split EQ 'POST'
                Locate 'SPLIT' in WfrStages using @VM setting StartPos then
                    StartPos += 1
                    NumStages = DCount(WfrStages, @VM)
                    For StageIndex = StartPos to NumStages
                        Stage = WfrStages<0, StageIndex>
                    Until Stage EQ 'DISP'
                        StageList<0, -1> = Stage
                    Next StageIndex                   
                end
 
            Case Otherwise$
                Error_Services('Add', 'Invalid Split value supplied to service ':Service:'.')
        End Case
        
        If Error_Services('NoError') then
            Convert '*' to '.' in WfrID
            For each Stage in StageList using @VM
                RunStageWfrKey = RDSNo:'*':Stage:'*':WfrID
                OutOfSpecArray = Xlate('RUN_STAGE_WFR', RunStageWfrKey, 'PARAM_OUT_OF_SPEC', 'X')
                DataOutOfSpec = ( Index(OutOfSpecArray, 1, 1) > 0 )
            Until DataOutOfSpec EQ True$
            Next Stage
        end
        Response = DataOutOfSpec
    end else
        Error_Services('Add', 'Null RDSNo, WfrID, or Split value supplied to service ':Service:'.')
    end
end service


//----------------------------------------------------------------------------------------------------------------------
// SplitSelection
//
//
//----------------------------------------------------------------------------------------------------------------------
Service SplitSelection(RDSNo)

    // Check if we need to auto-characterize the wafer.
    If RDSNo NE '' then
        PSNo           = Xlate('RDS', RDSNo, 'PROD_SPEC_ID', 'X')
        PSRec          = Database_Services('ReadDataRow', 'PROD_SPEC', PSNo)
        ANKO           = PSRec<PROD_SPEC_ANKO$>
        ANKOMetPockets = PSRec<PROD_SPEC_ANKO_MET_POCKETS$>
        ANKOCharSel    = PSRec<PROD_SPEC_ANKO_WAFER_SELECTION$>
        TechType       = PSRec<PROD_SPEC_TECH_TYPE$>
        ReactRunRec    = Database_Services('ReadDataRow', 'REACT_RUN', RDSNo)
        InWfrIDs       = ReactRunRec<REACT_RUN_IN_WFR_ID$>
        OutOfSpecArray = ReactRunRec<REACT_RUN_DATA_OUT_OF_SPEC$>
        CassNo         = ReactRunRec<REACT_RUN_CASS_NO$>
        WONo           = ReactRunRec<REACT_RUN_WO_NO$>
        GaNCritStages  = ReactRunRec<REACT_RUN_GAN_CRITICAL_STAGES$>
        GaNParamKeys   = ReactRunRec<REACT_RUN_GAN_PARAM_KEYS$>
        RunNo          = CassNo
        WOLogRec       = Database_Services('ReadDataRow', 'WO_LOG', WONo)
        SplitArray     = ''       
        CharStatus     = Xlate('REACT_RUN', RDSNo, 'CHAR_STATUS', 'X')
        // Is characterization due for this run?
        CharReq        = (CharStatus _EQC 'Sched' ) or (Sum(ANKOMetPockets) GT 0)
        NumWfrsOOS     = 0
        If CharReq EQ True$ then
            CharWfrIndex        = ''
            If ANKOCharSel EQ 'PRESET' then
                // 'Preset' char selection
                CharWfrIndex = PSRec<PROD_SPEC_ANKO_FULL_CHAR_POCKET$>
                // Default fail safe - set char wafer index to 1
                If CharWfrIndex EQ '' then CharWfrIndex = 1
            end else
                // 'Candela Total Defects' char selection algorithm
                If ANKO EQ True$ then
                    For each Value in OutOfSpecArray using @VM setting vPos
                        ANKOPocket = ANKOMetPockets<0, vPos>
                        If ( (ANKOPocket EQ True$) and (Value EQ True$) ) then
                            NumWfrsOOS += Value
                        end
                    Next Value
                end else
                    NumWfrsOOS = Sum(OutOfSpecArray)
                end
                If ANKO EQ True$ then
                    NumCriticalFailWfrs = Xlate('REACT_RUN', RDSNo, 'NUM_ANKO_CRITICAL_FAIL_WFRS', 'X')
                end else
                    NumCriticalFailWfrs = Xlate('REACT_RUN', RDSNo, 'NUM_CRITICAL_FAIL_WFRS', 'X')
                end
                NumWfrsOOS -= NumCriticalFailWfrs                           
                Begin Case
                    
                    Case NumWfrsOOS EQ 0
                        // Select the wafer with the highest candela total defects value
                        DefectArray = ''                   
                        WorstWfrIndex = 1
                        For each WfrID in InWfrIDs using @VM setting WfrIndex
                            ANKOPocket      = ANKOMetPockets<0, WfrIndex>
                            If ( (ANKO EQ True$) and (ANKOPocket NE True$) ) then
                                // Don't consider this wafer for split selection
                                Null
                            end else
                                // Consider this wafer for split selection
                                CriticalFailure = Xlate('WO_WFR', WfrID, 'CRITICAL_FAILURE', 'X')                         
                                If CriticalFailure NE True$ then 
                                    CANTotalDefects = 0
                                    Convert '*' to '.' in WfrID
                                    // Try to use CAN_PST data if it exists, otherwise fallback on CAN_PRE data.
                                    Locate 'CAN_PST' in GaNCritStages using @VM setting sPos then
                                        GaNParamKey = GanParamKeys<0, sPos>
                                        MetNames    = Xlate('GAN_PARAMS', GaNParamKey, 'MET_NAME', 'X')
                                        Locate 'Candela Total Defects Post' in MetNames using @VM setting mPos then
                                            RunStageWfrKey  = RDSNo:'*CAN_PST*':WfrID
                                            If RowExists('RUN_STAGE_WFR', RunStageWfrKey) then                           
                                                MetValues       = Xlate('RUN_STAGE_WFR', RunStageWfrKey, 'MET_PARAM_VALUES', 'X')
                                                CANTotalDefects = MetValues<0, mPos>
                                                If CANTotalDefects NE '' then
                                                    DefectArray<0, WfrIndex> = CANTotalDefects
                                                    WorstWfrDefects = DefectArray<0, WorstWfrIndex>
                                                    If CANTotalDefects GT WorstWfrDefects then
                                                        WorstWfrIndex = WfrIndex                            
                                                    end
                                                end
                                            end else
                                                Locate 'CAN_PRE' in GaNCritStages using @VM setting sPos then
                                                    GaNParamKey = GanParamKeys<0, sPos>
                                                    MetNames    = Xlate('GAN_PARAMS', GaNParamKey, 'MET_NAME', 'X')
                                                    Locate 'Candela Total Defects' in MetNames using @VM setting mPos then
                                                        RunStageWfrKey  = RDSNo:'*CAN_PRE*':WfrID
                                                        If RowExists('RUN_STAGE_WFR', RunStageWfrKey) then                           
                                                            MetValues       = Xlate('RUN_STAGE_WFR', RunStageWfrKey, 'MET_PARAM_VALUES', 'X')
                                                            CANTotalDefects = MetValues<0, mPos>
                                                            If CANTotalDefects NE '' then
                                                                DefectArray<0, WfrIndex> = CANTotalDefects
                                                                WorstWfrDefects = DefectArray<0, WorstWfrIndex>
                                                                If CANTotalDefects GT WorstWfrDefects then
                                                                    WorstWfrIndex = WfrIndex                            
                                                                end
                                                            end
                                                        end
                                                    end    
                                                end    
                                            end
                                        end
                                    end else
                                        Locate 'CAN_PRE' in GaNCritStages using @VM setting sPos then
                                            GaNParamKey = GanParamKeys<0, sPos>
                                            MetNames    = Xlate('GAN_PARAMS', GaNParamKey, 'MET_NAME', 'X')
                                            Locate 'Candela Total Defects' in MetNames using @VM setting mPos then
                                                RunStageWfrKey  = RDSNo:'*CAN_PRE*':WfrID
                                                If RowExists('RUN_STAGE_WFR', RunStageWfrKey) then                           
                                                    MetValues       = Xlate('RUN_STAGE_WFR', RunStageWfrKey, 'MET_PARAM_VALUES', 'X')
                                                    CANTotalDefects = MetValues<0, mPos>
                                                    If CANTotalDefects NE '' then
                                                        DefectArray<0, WfrIndex> = CANTotalDefects
                                                        WorstWfrDefects = DefectArray<0, WorstWfrIndex>
                                                        If CANTotalDefects GT WorstWfrDefects then
                                                            WorstWfrIndex = WfrIndex                            
                                                        end
                                                    end
                                                end
                                            end    
                                        end
                                    end
                                end
                            end                        
                        Next WfrID
                        
                        CharWfrIndex = WorstWfrIndex
                        
                    Case NumWfrsOOS EQ 1
                        For each WfrID in InWfrIDs using @VM setting vPos
                            CriticalFailure = Xlate('WO_WFR', WfrID, 'CRITICAL_FAILURE', 'X')
                            If ( (OutOfSpecArray<0, vPos> EQ True$) and (CriticalFailure NE True$) ) then
                                WfrIndex = vPos
                            end
                        Next WfrID
                        ANKOPocket = ANKOMetPockets<0, WfrIndex>
                        If ( (ANKO EQ True$) and (ANKOPocket NE True$) ) then
                            // Don't consider this wafer for split selection
                            Null
                        end else
                            // Consider this wafer for split selection
                            // Use the out of spec wafer that hasn't failed critically
                            For each WfrID in InWfrIDs using @VM setting WfrIndex
                                CriticalFailure = Xlate('WO_WFR', WfrID, 'CRITICAL_FAILURE', 'X')   
                                If ( (OutOfSpecArray<0, WfrIndex> EQ True$) and (CriticalFailure NE True$) ) then
                                    CharWfrIndex = WfrIndex
                                end
                            Until CharWfrIndex NE ''
                            Next WfrID
                        end
                                            
                    Case NumWfrsOOS GT 1
                        // Select the out of spec wafer with the highest candela total defects value
                        DefectArray   = ''                   
                        WorstWfrIndex = 1
                        For each WfrID in InWfrIDs using @VM setting WfrIndex
                            ANKOPocket = ANKOMetPockets<0, WfrIndex>
                            If ( (ANKO EQ True$) and (ANKOPocket NE True$) ) then
                                // Don't consider this wafer for split selection
                                Null
                            end else
                                // Consider this wafer for split selection
                                CriticalFailure = Xlate('WO_WFR', WfrID, 'CRITICAL_FAILURE', 'X')   
                                If CriticalFailure NE True$ then
                                    WfrOOS = OutOfSpecArray<0, WfrIndex>
                                    If WfrOOS EQ True$ then
                                        CANTotalDefects = 0
                                        Convert '*' to '.' in WfrID
                                        // Try to use CAN_PST data if it exists, otherwise fallback on CAN_PRE data.
                                        Locate 'CAN_PST' in GaNCritStages using @VM setting sPos then
                                            GaNParamKey = GanParamKeys<0, sPos>
                                            MetNames    = Xlate('GAN_PARAMS', GaNParamKey, 'MET_NAME', 'X')
                                            Locate 'Candela Total Defects Post' in MetNames using @VM setting mPos then
                                                RunStageWfrKey  = RDSNo:'*CAN_PST*':WfrID
                                                If RowExists('RUN_STAGE_WFR', RunStageWfrKey) then                           
                                                    MetValues       = Xlate('RUN_STAGE_WFR', RunStageWfrKey, 'MET_PARAM_VALUES', 'X')
                                                    CANTotalDefects = MetValues<0, mPos>
                                                    If CANTotalDefects NE '' then
                                                        DefectArray<0, WfrIndex> = CANTotalDefects
                                                        WorstWfrDefects = DefectArray<0, WorstWfrIndex>
                                                        If CANTotalDefects GT WorstWfrDefects then
                                                            WorstWfrIndex = WfrIndex                            
                                                        end
                                                    end
                                                end else
                                                    Locate 'CAN_PRE' in GaNCritStages using @VM setting sPos then
                                                        GaNParamKey = GanParamKeys<0, sPos>
                                                        MetNames    = Xlate('GAN_PARAMS', GaNParamKey, 'MET_NAME', 'X')
                                                        Locate 'Candela Total Defects' in MetNames using @VM setting mPos then
                                                            RunStageWfrKey  = RDSNo:'*CAN_PRE*':WfrID
                                                            If RowExists('RUN_STAGE_WFR', RunStageWfrKey) then                           
                                                                MetValues       = Xlate('RUN_STAGE_WFR', RunStageWfrKey, 'MET_PARAM_VALUES', 'X')
                                                                CANTotalDefects = MetValues<0, mPos>
                                                                If CANTotalDefects NE '' then
                                                                    DefectArray<0, WfrIndex> = CANTotalDefects
                                                                    WorstWfrDefects = DefectArray<0, WorstWfrIndex>
                                                                    If CANTotalDefects GT WorstWfrDefects then
                                                                        WorstWfrIndex = WfrIndex                            
                                                                    end
                                                                end
                                                            end
                                                        end    
                                                    end    
                                                end
                                            end
                                        end else
                                            Locate 'CAN_PRE' in GaNCritStages using @VM setting sPos then
                                                GaNParamKey = GanParamKeys<0, sPos>
                                                MetNames    = Xlate('GAN_PARAMS', GaNParamKey, 'MET_NAME', 'X')
                                                Locate 'Candela Total Defects' in MetNames using @VM setting mPos then
                                                    RunStageWfrKey  = RDSNo:'*CAN_PRE*':WfrID
                                                    If RowExists('RUN_STAGE_WFR', RunStageWfrKey) then                           
                                                        MetValues       = Xlate('RUN_STAGE_WFR', RunStageWfrKey, 'MET_PARAM_VALUES', 'X')
                                                        CANTotalDefects = MetValues<0, mPos>
                                                        If CANTotalDefects NE '' then
                                                            DefectArray<0, WfrIndex> = CANTotalDefects
                                                            WorstWfrDefects = DefectArray<0, WorstWfrIndex>
                                                            If CANTotalDefects GT WorstWfrDefects then
                                                                WorstWfrIndex = WfrIndex                            
                                                            end
                                                        end
                                                    end
                                                end    
                                            end
                                        end
                                    end
                                end                
                            end  
                        Next WfrID                    
                        CharWfrIndex = WorstWfrIndex
                End Case
            end
 
            For each WfrID in InWfrIDs using @VM setting WfrIndex
                ANKOPocket = ANKOMetPockets<0, WfrIndex>
                Begin Case
                    Case ( (WfrIndex EQ CharWfrIndex) and (CharStatus EQ 'Sched') )
                        SplitArray<0, WfrIndex> = 'CHAR'
                    Case ( ( (TechType EQ 'P' ) ) and (ANKOPocket EQ True$) )
                        SplitArray<0, WfrIndex> = 'PARTIAL_CHAR'
                    Case Otherwise$
                        SplitArray<0, WfrIndex> = 'DISP'
                End Case
            Next WfrID
            
        end else
            // Disposition all wafers
            For each WfrID in InWfrIDs using @VM setting WfrIndex
                SplitArray<0, WfrIndex> = 'DISP'
            Next WfrID
        end

        // Disposition and characterize wafers according to the data from our split array.
        Auto = True$
        For each WfrID in InWfrIDs using @VM setting WfrIndex
            SplitAction = SplitArray<0, WfrIndex>
            Begin Case
                Case SplitAction EQ 'PARTIAL_CHAR'
                    Gan_Services('CharacterizeWafer', RDSNo, WfrID, Auto, True$)
                Case SplitAction EQ 'CHAR'
                    Gan_Services('CharacterizeWafer', RDSNo, WfrID, Auto, False$)
                Case SplitAction EQ 'DISP'
                    Gan_Services('DispositionWfr', RDSNo, WfrID, Auto)
                Case Otherwise$
                    ErrorMsg = 'Error splitting wafer ':WfrID:' in the ':Service:' service.'
                    Error_Services('Add', ErrorMsg)
            End Case
        Next WfrID
    end                        

end service


//----------------------------------------------------------------------------------------------------------------------
// GetCharType
//
// Input:
//      WfrID    - [Required]
//
// Output:
//      Response - 'Light' if light characterization performed on wafer, 'Full' if destructive characterization
//                 performed on wafer, or '' if no characterization perfomed on wafer.
//
//----------------------------------------------------------------------------------------------------------------------
Service GetCharType(WfrID)
    
    CharType = ''
    If WfrID NE '' then
        RDSNo = Xlate('WO_WFR', WfrID, 'RDS_NO', 'X')
        If (RDSNo NE '') then
            ReactRunRec = Database_Services('ReadDataRow', 'REACT_RUN', RDSNo)
            InWfrIDs  = ReactRunRec<REACT_RUN_IN_WFR_ID$>
            CharFlags = ReactRunRec<REACT_RUN_CHAR_WFR_FLAG$>
            Locate WfrID in InWfrIDs using @VM setting WfrIndex then
                WfrCharacterized = CharFlags<0, WfrIndex>
                If WfrCharacterized EQ True$ then
                    WfrDestroyed = Gan_Services('GetDestroyedStatus', WfrID)
                    If WfrDestroyed EQ True$ then
                        CharType = 'Full'
                    end else
                        CharType = 'Light'
                    end
                end
            end else
                Error_Services('Add', 'Error locating WfrID ':WfrID:' in RDS record ':RDSNo:' in service ':Service:'.')
            end
        end else
            Error_Services('Add', 'Error retrieving RDS number from WO_WFR record ':WfrID:' in service ':Service:'.')
        end        
    end else
        Error_Services('Add', 'Null WfrID key supplied to service ':Service:'.')
    end
    Response = CharType
    
end service


//----------------------------------------------------------------------------------------------------------------------
// ReadyToClose
//
//
//----------------------------------------------------------------------------------------------------------------------
Service ReadyToClose(RDSNo)
    
    Response = False$
    If RDSNo NE '' then
        ReadyToClose = True$
        ReactRunRec = Database_Services('ReadDataRow', 'REACT_RUN', RDSNo)
        WfrsDispReady = ReactRunRec<REACT_RUN_WFRS_DISP_READY$>
        For each WfrStatus in WfrsDispReady using @VM
            If WfrStatus NE True$ then
                ReadyToClose = False$
            end
        Until ReadyToClose = False$
        Next WfrStatus
        Response = ReadyToClose        
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// CloseRDS
//
//
//----------------------------------------------------------------------------------------------------------------------
Service CloseRDS(RDSNo)

    If RDSNo NE '' then
        // Is the RDS eligible to be closed?
        CurrDTM       = Datetime()
        ReactRunRec   = Database_Services('ReadDataRow', 'REACT_RUN', RDSNo)
        WfrsDispReady = ReactRunRec<REACT_RUN_WFRS_DISP_READY$>
        InWfrIDs      = ReactRunRec<REACT_RUN_IN_WFR_ID$>
        EligibleToClose = True$ ; // Assume ready to close until proven otherwise
        For each WfrStatus in WfrsDispReady using @VM setting WfrIndex
            If WfrStatus NE True$ then EligibleToClose = False$
        Until EligibleToClose EQ False$
        Next WfrStatus
        If EligibleToClose EQ True$ then           
            // 1. Move eligble wafers to outbound box.            
            ShipFlags   = ReactRunRec<REACT_RUN_CARR_WFR_SHIP$>
            OutSlotIDs  = ReactRunRec<REACT_RUN_OUT_SLOT_ID$>           
            WONo        = ReactRunRec<REACT_RUN_WO_NO$>
            WOLogRec    = Database_Services('ReadDataRow', 'WO_LOG', WONo)
            WOMatKeys   = WOLogRec<WO_LOG_WO_MAT_KEY$>
            CustNo      = WOLogRec<WO_LOG_CUST_NO$>
            EpiPartNo   = WOLogRec<WO_LOG_EPI_PART_NO$>
            CustEpiNo   = CustNo:'*':EpiPartNo
            SchedQty    = Xlate('CUST_EPI_PART', CustEpiNo, 'CASS_SHIP_QTY', 'X')
            NumCass     = DCount(WOMatKeys, @VM)
            OutCassQtys = Xlate('WO_MAT_WFR', WOMatKeys, 'OUT_WFR_QTY', 'X')
            FQASigs     = Xlate('WO_MAT', WOMatKeys, 'CASS_FINAL_SIG', 'X')
			PSNo        = Xlate('REACT_RUN', RDSNo, 'PS_NO', 'X')
            ANKO        = Xlate('PROD_SPEC', PSNo, 'ANKO', 'X')
            CurrPartNo  = Xlate('PROD_SEPC', PSNo, 'CURR_PART_NO', 'X')											   
			
            For each InWfrID in InWfrIDs using @VM setting WfrIndex
                NextOutSlotID = ''
                ShipFlag  = ShipFlags<0, WfrIndex>
                OutSlotID = OutSlotIDs<0, WfrIndex>
                If ( (ShipFlag EQ True$) and (OutSlotID EQ '') and (ANKO NE True$) ) then
*                If ( (ShipFlag EQ True$) and (OutSlotID EQ '') and (ANKO NE True$) and (CurrPartNo NE 'U314') ) then
                    Gan_Services('AddWfrToOutCass', InWfrID)
                end else
                    // Remove the wafer from the WIP. These wafers should be in either DISP or RETAIN
                    CurrStage = GaN_Services('GetCurrStage', InWfrID)
                    CurrQ     = GaN_Services('GetWfrQueue', InWfrID)
                    If CurrQ NE '' then Gan_Services('RemoveWfrFromWIP', InWfrID)
                    If CurrStage NE '' then
                        // Mark current stage complete
                        Convert '*' to '.' in InWfrID
                        RunStageWfrKey = RDSNo:'*':CurrStage:'*':InWfrID
                        RunStageWfrRec = Database_Services('ReadDataRow', 'RUN_STAGE_WFR', RunStageWfrKey)
                        StageStatus    = RunStageWfrRec<RUN_STAGE_WFR_STATUS$>
                        If StageStatus _EQC 'INIT' then
                            // Mark DISP stage as complete
                            RunStageWfrRec<RUN_STAGE_WFR_COMP_BY$>  = @USER4
                            RunStageWfrRec<RUN_STAGE_WFR_COMP_DTM$> = CurrDTM
                            RunStageWfrRec<RUN_STAGE_WFR_STATUS$>   = 'COMP'
                            Database_Services('WriteDataRow', 'RUN_STAGE_WFR', RunStageWfrKey, RunStageWfrRec, True$, False$, True$)
                        end
                    end
                end                               
            Next InWfrID

            If Error_Services('NoError') then
                // Get an up-to-date copy of the REACT_RUN record
                ReactRunRec = Database_Services('ReadDataRow', 'REACT_RUN', RDSNo)
                // Mark the RDS "Closed"
                ReactRunRec<REACT_RUN_DISP_COMPLETE$> = True$
                Database_Services('WriteDataRow', 'REACT_RUN', RDSNo, ReactRunRec, True$, False$, True$)
                Engineering_Services('ClearEARequests', RDSNo)
            end
        end else
            If Assigned(WfrIndex) then
                WfrID = InWfrIDs<0, WfrIndex>
                Error_Services('Add', 'Error closing RDS. Wafer ':WfrID:' has incomplete metrology stages.')
            end else
                Error_Services('Add', 'Error closing RDS')
            end            
        end       
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// UpdateDataFlags
//
//
//----------------------------------------------------------------------------------------------------------------------
Service UpdateDataFlags(RDSNo)

    Response = ''
    If RDSNo NE '' then
        ReactRunRec      = Database_Services('ReadDataRow', 'REACT_RUN', RDSNo)
        OrigReactRunRec  = ReactRunRec
        InWfrIDs         = ReactRunRec<REACT_RUN_IN_WFR_ID$>
        CharFlags        = ReactRunRec<REACT_RUN_CHAR_WFR_FLAG$>
        CritStages       = ReactRunRec<REACT_RUN_GAN_CRITICAL_STAGES$>
        RunScrapped      = False$
        // Clear failure flags
        ReactRunRec<REACT_RUN_RUN_SCRAPPED$> = RunScrapped
        ReactRunRec<REACT_RUN_DATA_MISSING$> = ''											 										  
        GaNMetStages     = Database_Services('ReadDataRow', 'APP_INFO', 'GAN_MET_STAGES')       

        For each WfrID in InWfrIDs using @VM setting WaferPos
            // Assume flags are false for this wafer until proven otherwise.
            DataValidationDisabled = ReactRunRec<REACT_RUN_MISSING_DATA_VALID_DISABLED$, WaferPos>  
            DataIsMissing = False$
            DataOutOfSpec = False$
            For each Stage in GaNMetStages using @FM setting StagePos
                Abort = False$
                If Abort EQ False$ then
                    Locate Stage in CritStages using @VM setting vPos then
                        PostSplit          = ''
                        PostSplitFailure   = False$
                        WaferCharacterized = ''
                        PostSplit          = (StagePos GT 4)
                        WaferCharacterized = CharFlags<0, WaferPos>
                        Convert '*' to '.' in WfrID
																		  
                        RunStageWfrKey     = RDSNo:'*':Stage:'*':WfrID
                        If RowExists('RUN_STAGE_WFR', RunStageWfrKey) then
                            RunStageWfrRec = Database_Services('ReadDataRow', 'RUN_STAGE_WFR', RunStageWfrKey)
                            OutOfSpecArray = RunStageWfrRec<RUN_STAGE_WFR_PARAM_OUT_OF_SPEC$>                            
																   
                            For each Parameter in OutOfSpecArray using @VM setting vPos
                                If DataIsMissing EQ False$ then
                                    // Check if we need to set the flag to true
                                    Locate '-1' in Parameter using @SVM setting pPos then
                                        DataIsMissing = True$
                                    end
                                end
                                Locate '1' in Parameter using @SVM setting pPos then
                                    DataOutOfSpec = True$
                                    If PostSplit EQ True$ then PostSplitFailure = True$
                                end                  
                            Next Parameter                                                         
									   
                            If RunScrapped NE True$ then
                                // Check if we need to set the flag to true								   
                                If ( (PostSplitFailure EQ True$) and (WaferCharacterized EQ True$) and (DataValidationDisabled NE True$) ) then
                                    RunScrapped = True$
                                    ReactRunRec<REACT_RUN_RUN_SCRAPPED$> = RunScrapped
                                end 
                            end            
                        end
                    end
                end               
            Next Stage
            
            ReactRunRec<REACT_RUN_DATA_MISSING$, WaferPos>     = DataIsMissing
            ReactRunRec<REACT_RUN_DATA_OUT_OF_SPEC$, WaferPos> = DataOutOfSpec
            If ( (DataIsMissing EQ False$) and (DataOutOfSpec EQ False$) ) then
                ReactRunRec<REACT_RUN_MISSING_DATA_VALID_DISABLED$, WaferPos> = False$
                ReactRunRec<REACT_RUN_MISSING_DATA_VALID_USER$, WaferPos> = ''
                ReactRunRec<REACT_RUN_MISSING_DATA_VALID_DTM$, WaferPos> = ''
            end
            
        Next WfrID

        If OrigReactRunRec NE ReactRunRec then
            Database_Services('WriteDataRow', 'REACT_RUN', RDSNo, ReactRunRec, True$, False$, True$)
        end
    end
       
end service


//----------------------------------------------------------------------------------------------------------------------
// GetGaNMetrologyData
//
//
//----------------------------------------------------------------------------------------------------------------------
Service GetGaNMetrologyData(RDSNo, Stage, Tool, WfrID)

    Response       = ''
    ErrorMsg       = ''
    OICandelaStage = ''
    Abort          = False$
    If ( (RDSNo NE '') and (Stage NE '') ) then
        ReactRunRec      = Database_Services('ReadDataRow', 'REACT_RUN', RDSNo)
        OrigReactRunRec  = ReactRunRec
        PSNo             = Xlate('REACT_RUN', RDSNo, 'PS_NO', 'X')
        NCRFlags         = ReactRunRec<REACT_RUN_NCR_REQ$>
        InWfrIDs         = ReactRunRec<REACT_RUN_IN_WFR_ID$>
        DispFlags        = ReactRunRec<REACT_RUN_WFRS_DISP_READY$>
        Grades           = ReactRunRec<REACT_RUN_CARR_WFR_GRADE$>
        Reasons          = ReactRunRec<REACT_RUN_DISP_REASON$>
        CharFlags        = ReactRunRec<REACT_RUN_CHAR_WFR_FLAG$>
        RunScrapped      = ReactRunRec<REACT_RUN_RUN_SCRAPPED$>

        If Abort EQ False$ then
            TechType         = Xlate('PROD_SPEC', PSNo, 'TECH_TYPE', 'X')
            GaNMetStages     = Database_Services('ReadDataRow', 'APP_INFO', 'GAN_MET_STAGES')
            If WfrID NE '' then Scribe = Xlate('WO_WFR', WfrID, 'SCRIBE', 'X')
            
            GaNParamKey  = ''
            GaNParamKeys = Xlate('REACT_RUN', RDSNo, 'GAN_PARAM_KEYS', 'X')
            Found = False$
            For each GaNParamKey in GaNParamKeys using @VM setting vPos
                Loc = Index(GaNParamKey, Stage, 1)
                If Loc GT 0 then Found = True$
            Until Found EQ True$
            Next GaNParamKey
            
            GaNParamRec = Database_Services('ReadDataRow', 'GAN_PARAMS', GaNParamKey)
            MetNames    = GaNParamRec<GAN_PARAMS.MET_NAME$>
            LSLs        = GaNParamRec<GAN_PARAMS.LSL$>
            USLs        = GaNParamRec<GAN_PARAMS.USL$>
			Targets     = GaNParamRec<GAN_PARAMS.TARGET$>										 
            RunNo       = ReactRunRec<REACT_RUN_GAN_RUN_ID$>
            Reactor     = 'R':ReactRunRec<REACT_RUN_REACTOR$>
            PartNo      = Xlate('REACT_RUN', RDSNo, 'EPI_PART_NO', 'X')       

            Query = ''
            IQSToolID = Xlate('TOOL', Tool, 'IQS_ID', 'X')
            
            If Stage NE 'JV_XRD' then
                Query  = "SELECT td.F_NAME, se.F_TSNO, SE.F_SBNO, se.F_VAL "
            end else
                Query  = "SELECT td.F_NAME, SE.F_SBNO, se.F_TSNO, se.F_VAL "
            end
            Query := "FROM [IRMNSPC].[dbo].[SGRP_EXT] se "                                                          
            Query := "JOIN [IRMNSPC].[dbo].[PART_LOT] pl on se.F_LOT  = pl.F_LOT "                                  
            Query := "JOIN [IRMNSPC].[dbo].[TEST_DAT] td on se.F_TEST = td.F_TEST "                                 
            Query := "JOIN [IRMNSPC].[dbo].[PART_DAT] pd on se.F_PART = pd.F_PART "                                 
            Query := "JOIN [IRMNSPC].[dbo].[PRCS_DAT] pr on se.F_PRCS = pr.F_PRCS "
            Begin Case
                Case Stage EQ 'BV100%'
                    Query := "LEFT JOIN [IRMNSPC].[dbo].[SGRP_DSC] sd on se.F_SGRP = sd.F_SGRP "
                Case ( (Stage NE 'GROWTH') and (Stage NE 'TEMP') )
                    Query := "JOIN [IRMNSPC].[dbo].[SGRP_DSC] sd on se.F_SGRP = sd.F_SGRP "         
                    Query := "LEFT JOIN [IRMNSPC].[dbo].[DESC_DAT] dd on sd.F_DESC = dd.F_DESC "
            End Case                      
            Query := "WHERE pd.F_NAME = '":PartNo:"'	AND "                                                   
            Query := "      pr.F_NAME = '":Reactor:"'	AND "                                                       
            Query := "      pl.F_NAME = '":RunNo:"'		AND "
            If Stage EQ 'JV_XRD' then
                Query := "      (se.F_FLAG = 0 or se.F_FLAG = 2) AND "
            end else
                Query := "      se.F_FLAG = 0				AND "
            end
            Begin Case
                Case ( (Stage EQ 'WARP') or (Stage EQ 'GROWTH') or (Stage EQ 'TEMP') or (Stage EQ 'BV100%') )
                    Query := " ("
                Case ( (Stage EQ 'CAN') or (Stage EQ 'UV') or (Stage EQ 'UV_PRE') or (Stage EQ 'UV_PST') or (Stage EQ 'CAN_PRE') or (Stage EQ 'CAN_PST') ) 
                    Query := "    dd.F_NAME = '":IQSToolID:"'	AND ("
                Case Otherwise$
                    Query := "    dd.F_NAME = '":Scribe:"'		AND ("
            End Case
            
            NumMetNames = DCount(MetNames, @VM)
            For each MetName in MetNames using @VM setting vPos
                If vPos EQ NumMetNames then
                    Query := "td.F_NAME = '":MetName:"') "
                end else
                    Query := "td.F_NAME = '":MetName:"' or "
                end                    
            Next MetName                
            
            If ( (Stage NE 'GROWTH') and (Stage NE 'TEMP') and (Stage NE 'BV100%') ) then
                Query := "AND dd.F_NAME IS NOT NULL "
            end
            Query := "ORDER BY td.F_NAME"     

            If Query NE '' then Response = SQL_Services('PostSQLRequest', 'IQSDMS1', Query)
            Convert @FM to @VM in Response
            Convert @RM to @FM in Response
            ParamValues = ''

            For each MetName in MetNames using @VM setting mPos
      
                PointAvg  = 0
                NumPoints = 0
                For each Row in Response using @FM setting fPos
                    Locate MetName in Row using @VM setting vPos then
                        DataPointIndex = Response<fPos, 3>
                        If ( (Index(Row, @SVM, 1) GT 0) and (DataPointIndex EQ 0) ) else
                            // Data point at index 0 for parameters with multiple data points is an average.
                            // Ignore this data point.
                            WaferPos = Response<fPos, 2>
                            ParamValues<WaferPos, mPos, DataPointIndex> = Response<fPos, 4>
                        end
                    end
                Next Row
                
                If Stage EQ 'JV_XRD' then
                    TheseParamValues            = ParamValues<WaferPos, mPos>
                    NumPoints                   = DCount(TheseParamValues, @SVM)
                    If NumPoints GT 0 then ParamValues<WaferPos, mPos> = Sum(TheseParamValues) / NumPoints
                end
                                               
            Next MetName
            
            ChangeDetected = False$
            
            If ( (Stage EQ 'UV') or (Stage EQ 'UV_PRE') or (Stage EQ 'UV_PST') or (Stage EQ 'WARP') or (Stage EQ 'CAN') or (Stage EQ 'CAN_PRE') or (Stage EQ 'CAN_PST') or (Stage EQ 'GROWTH') or (Stage EQ 'TEMP') ) then
                               
                For each WfrID in InWfrIDs using @VM setting WaferPos
                    ValueSet        = ParamValues<WaferPos>                
                    Delimiter       = @VM
                    Convert '*' to '.' in WfrID
                    RunStageWfrKey  = RDSNo:'*':Stage:'*':WfrID																	   
                    RunStageWfrRec  = Database_Services('ReadDataRow', 'RUN_STAGE_WFR', RunStageWfrKey)
                    RunStageWfrRec<RUN_STAGE_WFR_MET_PARAM_VALUES$> = ValueSet
                    OutOfSpecArray  = ''
                    For each Parameter in MetNames using @VM setting vPos
                        Value       = ValueSet<0, vPos>
                        LSL         = LSLs<0, vPos>
                        USL         = USLs<0, vPos>
						Target      = Targets<0, vPos>
						MaxDecimals = GaN_Services('GetMaxDecimals', LSL:@VM:USL:@VM:Target)
						If Value NE '' then
							Value = SRP_Math('ROUND', Value, MaxDecimals)
							// Ensures decimal places are retained and zeros are padded.
							Value = Oconv(Iconv(Value, 'MD':MaxDecimals:'L'), 'MD':MaxDecimals:'L')
						end
						ValueSet<0,vPos> = Value
                        
                        // One value per parameter
                        Begin Case
                            Case (LSL NE '') and (Value NE '')
                                OutOfSpecArray<0, vPos> = (Value LT LSL)
                            Case (LSL NE '') and (Value EQ '')
                                OutOfSpecArray<0, vPos> = -1 ; // -1 = data value is missing
                                ErrorMsg = 'Error retrieving metrology data from SPC for RDS ':RDSNo:'.'
                            Case Otherwise$
                                OutOfSpecArray<0, vPos> = False$
                        End Case
                        
                        // Need to check if we have already failed
                        If (OutOfSpecArray<0, vPos> NE True$) then
                            Begin Case
                                Case (USL NE '') and (Value NE '')
                                    OutOfSpecArray<0, vPos> = (Value GT USL)
                                Case (USL NE '') and (Value EQ '')
                                    OutOfSpecArray<0, vPos> = -1 ; // -1 = data value is missing
                                    ErrorMsg = 'Error retrieving metrology data from SPC for RDS ':RDSNo:'.'
                                Case Otherwise$
                                    OutOfSpecArray<0, vPos> = False$
                            End Case
                        end

                    Next Parameter

					RunStageWfrRec<RUN_STAGE_WFR_MET_PARAM_VALUES$>  = ValueSet
                    RunStageWfrRec<RUN_STAGE_WFR_PARAM_OUT_OF_SPEC$> = OutOfSpecArray
                    Database_Services('WriteDataRow', 'RUN_STAGE_WFR', RunStageWfrKey, RunStageWfrRec, True$, False$, True$)                  
                    
                Next WfrID
                
            end else
                
                Delimiter          = @VM
                PostSplit          = ''
                WaferCharacterized = ''
                Locate Stage in GaNMetStages using @FM setting fPos then
                    If fPos LT 4 then
                        PostSplit = False$
                    end else          
                        PostSplit = True$
                    end				 
                end
			   
                Locate WfrID in InWfrIDs using @VM setting WaferPos then
                    WaferCharacterized = CharFlags<0, WaferPos>
                end
                Convert '*' to '.' in WfrID
                RunStageWfrKey = RDSNo:'*':Stage:'*':WfrID
                RunStageWfrRec = Database_Services('ReadDataRow', 'RUN_STAGE_WFR', RunStageWfrKey)
                RunStageWfrRec<RUN_STAGE_WFR_MET_PARAM_VALUES$> = ParamValues
                OutOfSpecArray = ''
                For each Parameter in MetNames using @VM setting vPos
                    Value = ParamValues<0, vPos>                    									 
                    LSL = LSLs<0, vPos>																 
                    USL = USLs<0, vPos>
					Target           = Targets<0, vPos>
					MaxDecimals      = GaN_Services('GetMaxDecimals', LSL:@VM:USL:@VM:Target)
                    If Index(Value, @SVM, 1) GT 0 then
                        // Multiple values per parameter
						RoundedValues = ''
                        For each SubValue in Value using @SVM setting svPos                            										 
                            Begin Case
                                Case (LSL NE '') and (SubValue NE '')
                                    OutOfSpecArray<0, vPos, svPos> = (SubValue LT LSL)
                                Case (LSL NE '') and (Subvalue EQ '')
                                    OutOfSpecArray<0, vPos, svPos> = -1 ; // -1 = data value is missing
                                    ErrorMsg = 'Error retrieving metrology data from SPC for RDS ':RDSNo:'.'
                                Case Otherwise$
                                    OutOfSpecArray<0, vPos, svPos> = False$
                            End Case
                            
                            // Need to check if we have already failed
                            If (OutOfSpecArray<0, vPos, svPos> NE True$) then
                                Begin Case
                                    Case (USL NE '') and (SubValue NE '')
                                        OutOfSpecArray<0, vPos, svPos> = (SubValue GT USL)
                                    Case (USL NE '') and (Subvalue EQ '')
                                        OutOfSpecArray<0, vPos, svPos> = -1 ; // -1 = data value is missing
                                        ErrorMsg = 'Error retrieving metrology data from SPC for RDS ':RDSNo:'.'
                                    Case Otherwise$
                                        OutOfSpecArray<0, vPos, svPos> = False$
                                End Case
                            end

                        Next SubValue
                        
                    end else
                        // One value per parameter
						If Value NE '' then         
							Value = SRP_Math('ROUND', Value, MaxDecimals)
							// Ensures decimal places are retained and zeros are padded.                        
							Value = Oconv(Iconv(Value, 'MD':MaxDecimals:'L'), 'MD':MaxDecimals:'L')
						end			
									  
                        Begin Case
                            Case (LSL NE '') and (Value NE '')
                                OutOfSpecArray<0, vPos> = (Value LT LSL)
                            Case (LSL NE '') and (Value EQ '')
                                OutOfSpecArray<0, vPos> = -1 ; // -1 = data value is missing
                                ErrorMsg = 'Error retrieving metrology data from SPC for RDS ':RDSNo:'.'
                            Case Otherwise$
                                OutOfSpecArray<0, vPos> = False$
                        End Case
                        
                        // Need to check if we have already failed
                        If (OutOfSpecArray<0, vPos> NE True$) then
                            Begin Case
                                Case (USL NE '') and (Value NE '')
                                    OutOfSpecArray<0, vPos> = (Value GT USL)
                                Case (USL NE '') and (Value EQ '')
                                    OutOfSpecArray<0, vPos> = -1 ; // -1 = data value is missing
                                    ErrorMsg = 'Error retrieving metrology data from SPC for RDS ':RDSNo:'.'
                                Case Otherwise$
                                    OutOfSpecArray<0, vPos> = False$
                            End Case
                        end
                    end

                Next Parameter
                RunStageWfrRec<RUN_STAGE_WFR_MET_PARAM_VALUES$>  = ParamValues
                RunStageWfrRec<RUN_STAGE_WFR_PARAM_OUT_OF_SPEC$> = OutOfSpecArray
                Database_Services('WriteDataRow', 'RUN_STAGE_WFR', RunStageWfrKey, RunStageWfrRec, True$, False$, True$)                           
            end
            If OrigReactRunRec NE ReactRunRec then
                Database_Services('WriteDataRow', 'REACT_RUN', RDSNo, ReactRunRec, True$, False$, True$)
            end
            If Stage EQ 'WARP' then GaN_Services('CheckForCriticalFailures', RDSNo, Tool)
        end
    end
    If ErrorMsg NE '' then Error_Services('Add', ErrorMsg)
    
end service


Service GetPlatoData(RDSNo, ToolID)

    If ( (RDSNo NE '') and (ToolID NE '') ) then
        WONo            = Xlate('REACT_RUN', RDSNo, 'WO_NO', 'X')
        EpiPartNo       = Xlate('WO_LOG', WONo, 'EPI_PART_NO', 'X')
        GaNRunID        = Xlate('REACT_RUN', RDSNo, 'GAN_RUN_ID', 'X')
        ReactRunRec     = Database_Services('ReadDataRow', 'REACT_RUN', RDSNo)
        OrigReactRunRec = ReactRunRec
        PSNo            = Xlate('REACT_RUN', RDSNo, 'PS_NO', 'X')
        NCRFlags        = ReactRunRec<REACT_RUN_NCR_REQ$>
        InWfrIDs        = ReactRunRec<REACT_RUN_IN_WFR_ID$>
        NumWfrs         = DCount(InWfrIDs, @VM)
        DispFlags       = ReactRunRec<REACT_RUN_WFRS_DISP_READY$>
        Grades          = ReactRunRec<REACT_RUN_CARR_WFR_GRADE$>
        Reasons         = ReactRunRec<REACT_RUN_DISP_REASON$>
        CharFlags       = ReactRunRec<REACT_RUN_CHAR_WFR_FLAG$>
        RunScrapped     = ReactRunRec<REACT_RUN_RUN_SCRAPPED$>
        GaNMetStages    = Database_Services('ReadDataRow', 'APP_INFO', 'GAN_MET_STAGES')
        GaNParamKey     = ''
        GaNParamKeys    = Xlate('REACT_RUN', RDSNo, 'GAN_PARAM_KEYS', 'X')
        Found           = False$
        For each GaNParamKey in GaNParamKeys using @VM setting vPos
            Loc = Index(GaNParamKey, 'PLATO', 1)
            If Loc GT 0 then Found = True$
        Until Found EQ True$
        Next GaNParamKey
        GaNParamRec = Database_Services('ReadDataRow', 'GAN_PARAMS', GaNParamKey)
        MetNames    = GaNParamRec<GAN_PARAMS.MET_NAME$>
        LSLs        = GaNParamRec<GAN_PARAMS.LSL$>
        USLs        = GaNParamRec<GAN_PARAMS.USL$>
        Targets     = GaNParamRec<GAN_PARAMS.TARGET$>										 
        RunNo       = ReactRunRec<REACT_RUN_GAN_RUN_ID$>
        Reactor     = 'R':ReactRunRec<REACT_RUN_REACTOR$>
        IQSToolID = Xlate('TOOL', ToolID, 'IQS_ID', 'X')
        Query = ''
        If ( (EpiPartNo NE '') and (GaNRunID NE '') ) then
            Query      = "select "
            Query     := "[Wafer Pocket], "
            Query     := "[Epi Thickness Mean Plato], "
            Query     := "[Plato Thickness Std Dev %] "
            Query     := "from ( "
            Query     := "  select se_sgrp, se_tsno, jd_name, pl_name, pd_name, "
            Query     := "         max(case when dg_name = 'Wafer ID' then dd_name end) [Wafer ID], "
            Query     := "         max(case when dg_name = 'Wafer Pocket' then dd_name end) [Wafer Pocket], "
            Query     := "         max(case when dg_name = 'Wafer Scribe' then dd_name end) [Wafer Scribe], "
            Query     := "         max(case when td_name = 'Epi Thickness Mean Plato' then se_val end) [Epi Thickness Mean Plato], "
            Query     := "         max(case when td_name = 'Plato Thickness Std Dev %' then se_val end) [Plato Thickness Std Dev %] "
            Query     := "  from ( "
            Query     := "    select se.f_sgrp se_sgrp, " 
            Query     := "           se.f_tsno se_tsno, "
            Query     := "           se.f_val se_val, "
            Query     := "           jd.f_name jd_name, "
            Query     := "           pl.f_name pl_name, "
            Query     := "           pd.f_name pd_name, "
            Query     := "           dg.f_name dg_name, "
            Query     := "           dd.f_name dd_name, "
            Query     := "           td.f_name td_name "
            Query     := "    from [IRMNSPC].[dbo].[SGRP_EXT] se "
            Query     := "    join [IRMNSPC].[dbo].[SGRP_DSC] sd "
            Query     := "      on se.f_sgrp = sd.f_sgrp "
            Query     := "     and se.f_tsno = sd.f_tsno "
            Query     := "    join [IRMNSPC].[dbo].[JOB_DAT] jd "
            Query     := "      on se.f_job = jd.f_job "
            Query     := "    join [IRMNSPC].[dbo].[PART_LOT] pl "
            Query     := "      on se.f_lot = pl.f_lot "
            Query     := "    join [IRMNSPC].[dbo].[PART_DAT] pd "
            Query     := "      on se.f_part = pd.f_part "
            Query     := "    join [IRMNSPC].[dbo].[DESC_DAT] dd "
            Query     := "      on sd.f_dsgp = dd.f_dsgp "
            Query     := "     and sd.f_desc = dd.f_desc "
            Query     := "    join [IRMNSPC].[dbo].[DESC_GRP] dg "
            Query     := "      on sd.F_DSGP = dg.F_DSGP "
            Query     := "    join [IRMNSPC].[dbo].[TEST_DAT] td "
            Query     := "      on se.F_TEST = td.F_TEST "
            Query     := "    where se.F_FLAG  = 0 "
            Query     := "      and jd.f_name = '":IQSToolID:"' "
            Query     := "      and pl.f_name = '":GaNRunID:"' "
            Query     := "      and pd.f_name = '":EpiPartNo:"' "
            Query     := "      and dg.f_name in ( "
            Query     := "        'Wafer ID', "
            Query     := "        'Wafer Pocket', "
            Query     := "        'Wafer Scribe' "
            Query     := "      ) "
            Query     := "      and td.f_name in ( "
            Query     := "        'Epi Thickness Mean Plato', "
            Query     := "        'Plato Thickness Std Dev %' "
            Query     := "      ) "
            Query     := "  ) as tableA "
            Query     := "  group by se_sgrp, "
            Query     := "           se_tsno, "
            Query     := "           jd_name, "
            Query     := "           pl_name, "
            Query     := "           pd_name "
            Query     := ") as tableB "
            Query     := "where [Wafer Scribe] != '<blank>' "
            Query     := "  and [Wafer ID] != '00' "
            Query     := "order by se_sgrp desc, "
            Query     := "         se_tsno"
            
            If Query NE '' then Response = SQL_Services('PostSQLRequest', 'IQSDMS1', Query)
            Convert @FM to @VM in Response
            Convert @RM to @FM in Response
            ParamValues = ''
            NumRows     = DCount(Response, @FM)
            For RowIndex = NumRows to 1 Step -1
                Row                    = Response<RowIndex>
                Pocket                 = Row<0, 1>
                EpiThickMean           = Row<0, 2>
                EpiStdDev              = Row<0, 3>
                ParamValues<Pocket, 1> = EpiThickMean
                ParamValues<Pocket, 2> = EpiStdDev
            Next RowIndex
            
            ChangeDetected = False$
                               
            For each WfrID in InWfrIDs using @VM setting WaferPos
                ValueSet        = ParamValues<WaferPos>                
                Delimiter       = @VM
                Convert '*' to '.' in WfrID
                RunStageWfrKey  = RDSNo:'*PLATO*':WfrID																	   
                RunStageWfrRec  = Database_Services('ReadDataRow', 'RUN_STAGE_WFR', RunStageWfrKey)
                RunStageWfrRec<RUN_STAGE_WFR_MET_PARAM_VALUES$> = ValueSet
                OutOfSpecArray  = ''
                For each Parameter in MetNames using @VM setting vPos
                    Value       = ValueSet<0, vPos>
                    LSL         = LSLs<0, vPos>
                    USL         = USLs<0, vPos>
                    Target      = Targets<0, vPos>
                    MaxDecimals = GaN_Services('GetMaxDecimals', LSL:@VM:USL:@VM:Target)
                    If Value NE '' then
                        Value = SRP_Math('ROUND', Value, MaxDecimals)
                        // Ensures decimal places are retained and zeros are padded.
                        Value = Oconv(Iconv(Value, 'MD':MaxDecimals:'L'), 'MD':MaxDecimals:'L')
                    end
                    ValueSet<0,vPos> = Value
                    
                    // One value per parameter
                    Begin Case
                        Case (LSL NE '') and (Value NE '')
                            OutOfSpecArray<0, vPos> = (Value LT LSL)
                        Case (LSL NE '') and (Value EQ '')
                            OutOfSpecArray<0, vPos> = -1 ; // -1 = data value is missing
                            ErrorMsg = 'Error retrieving metrology data from SPC for RDS ':RDSNo:'.'
                        Case Otherwise$
                            OutOfSpecArray<0, vPos> = False$
                    End Case
                    
                    // Need to check if we have already failed
                    If (OutOfSpecArray<0, vPos> NE True$) then
                        Begin Case
                            Case (USL NE '') and (Value NE '')
                                OutOfSpecArray<0, vPos> = (Value GT USL)
                            Case (USL NE '') and (Value EQ '')
                                OutOfSpecArray<0, vPos> = -1 ; // -1 = data value is missing
                                ErrorMsg = 'Error retrieving metrology data from SPC for RDS ':RDSNo:'.'
                            Case Otherwise$
                                OutOfSpecArray<0, vPos> = False$
                        End Case
                    end

                Next Parameter
                
                RunStageWfrRec<RUN_STAGE_WFR_MET_PARAM_VALUES$>  = ValueSet
                RunStageWfrRec<RUN_STAGE_WFR_PARAM_OUT_OF_SPEC$> = OutOfSpecArray
                Database_Services('WriteDataRow', 'RUN_STAGE_WFR', RunStageWfrKey, RunStageWfrRec, True$, False$, True$)                  
                
            Next WfrID
        end
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// CheckForCriticalFailures
//
//
//----------------------------------------------------------------------------------------------------------------------
Service CheckForCriticalFailures(RDSNo, Tool)

    If RDSNo NE '' then
        ReactRunRec     = Database_Services('ReadDataRow', 'REACT_RUN', RDSNo)
        OrigReactRunRec = ReactRunRec
        InWfrIDs        = ReactRunRec<REACT_RUN_IN_WFR_ID$>
        PSNo            = Xlate('REACT_RUN', RDSNo, 'PS_NO', 'X')
        TechType        = Xlate('PROD_SPEC', PSNo, 'TECH_TYPE', 'X')
        GaNMetStages    = Database_Services('ReadDataRow', 'APP_INFO', 'GAN_MET_STAGES')                                    
        RunNo           = ReactRunRec<REACT_RUN_GAN_RUN_ID$>
        Reactor         = 'R':ReactRunRec<REACT_RUN_REACTOR$>
        PartNo          = Xlate('REACT_RUN', RDSNo, 'EPI_PART_NO', 'X')               
        IQSToolID       = Xlate('TOOL', Tool, 'IQS_ID', 'X')
                                                               
        Query  = "SELECT td.F_NAME, se.F_TSNO, SE.F_SBNO, se.F_VAL "                                            
        Query := "FROM [IRMNSPC].[dbo].[SGRP_EXT] se "                                                          
        Query := "JOIN [IRMNSPC].[dbo].[PART_LOT] pl on se.F_LOT  = pl.F_LOT "                                  
        Query := "JOIN [IRMNSPC].[dbo].[TEST_DAT] td on se.F_TEST = td.F_TEST "                                 
        Query := "JOIN [IRMNSPC].[dbo].[PART_DAT] pd on se.F_PART = pd.F_PART "                                 
        Query := "JOIN [IRMNSPC].[dbo].[PRCS_DAT] pr on se.F_PRCS = pr.F_PRCS "                                 
        Query := "JOIN [IRMNSPC].[dbo].[SGRP_DSC] sd on se.F_SGRP = sd.F_SGRP "                                 
        Query := "LEFT JOIN [IRMNSPC].[dbo].[DESC_DAT] dd on sd.F_DESC = dd.F_DESC "                            
        Query := "WHERE pd.F_NAME = '":PartNo:"'	AND "                                                   
        Query := "      pr.F_NAME = '":Reactor:"'	AND "                                                       
        Query := "      pl.F_NAME = '":RunNo:"'		AND "                                                       
        Query := "      se.F_FLAG = 0				AND "
        Query := " ("

        MetNames = 'Bow':@VM:'Warp'
        NumMetNames = DCount(MetNames, @VM)
        For each MetName in MetNames using @VM setting vPos
            If vPos EQ NumMetNames then
                Query := "td.F_NAME = '":MetName:"') AND "
            end else
                Query := "td.F_NAME = '":MetName:"' or "
            end                    
        Next MetName                
        
        Query := "dd.F_NAME IS NOT NULL "
        Query := "ORDER BY td.F_NAME"     
                             
        If Query NE '' then Response = SQL_Services('PostSQLRequest', 'IQSDMS1', Query)

        If ( (Response NE '') and (Response NE 0) ) then
            Convert @FM to @VM in Response
            Convert @RM to @FM in Response
            ParamValues = ''
                             
            For each MetName in MetNames using @VM setting mPos  
                For each Row in Response using @FM setting fPos
                    Locate MetName in Row using @VM setting vPos then
                        DataPointIndex = Response<fPos, 3>
                        If ( (Index(Row, @SVM, 1) GT 0) and (DataPointIndex EQ 0) ) else
                            // Data point at index 0 for parameters with multiple data points is an average.
                            // Ignore this data point. 
                            WaferPos = Response<fPos, 2>                    
                            ParamValues<WaferPos, mPos, DataPointIndex> = Response<fPos, 4>
                        end
                    end
																					
                Next Row                                           
            Next MetName

            For each WfrID in InWfrIDs using @VM setting WaferPos
                
                WOWfrRec        = Database_Services('ReadDataRow', 'WO_WFR', WfrID)
                OrigCritFailure = WOWfrRec<WO_WFR_CRITICAL_FAILURE$>
                CriticalFailure = False$
                ValueSet        = ParamValues<WaferPos>                
                Delimiter       = @VM
                OutOfSpecArray  = ''
                For each Parameter in MetNames using @VM setting vPos
                    Value       = ValueSet<0, vPos>                               
                    // Check critical parameters
                    If Parameter EQ 'Bow' then
                        CriticalMin = Xlate('PROD_SPEC', PSNo, 'CRITICAL_BOW_MIN', 'X')
                        CriticalMax = Xlate('PROD_SPEC', PSNo, 'CRITICAL_BOW_MAX', 'X')
                        If ( (CriticalMin NE '') and (CriticalMax NE '') ) then
                            If Value LT CriticalMin or Value GT CriticalMax then CriticalFailure = True$
                        end
                    end
										  
                    If Parameter EQ 'Warp' then
                        CriticalMax = Xlate('PROD_SPEC', PSNo, 'CRITICAL_WARP_MAX', 'X')
                        If CriticalMax NE '' then
                            If Value GT CriticalMax then CriticalFailure = True$
                        end
                    end
				   
                Next Parameter                 
                
                Begin Case
                    Case ( (CriticalFailure EQ True$) and (OrigCritFailure NE True$) )                    
                        GaN_Services('AbortWafer', WfrID)
                    Case ( (CriticalFailure EQ False$) and (OrigCritFailure EQ True$) )                    
                        GaN_Services('RestoreWafer', WfrID)
                    Case Otherwise$
                        Null
                End Case

            Next WfrID     
        end
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// ConsumeInboundMaterial
//
//
//----------------------------------------------------------------------------------------------------------------------
Service ConsumeInboundMaterial(InWfrIDs)
    
    If InWfrIDs NE ''then
        For each InWfrID in InWfrIDs using @VM
            // Mark inbound material consumed (i.e. move WfrID to PrevWfrID within WO_MAT_WFR record)
            WONo                 = Field(InWfrID, '*', 1, 1)
            InboundCass          = Field(InWfrID, '*', 2, 1)
            SlotNo               = Field(InWfrID, '*', 3, 1)
            WOMatWfrKey          = WONo:'*':InboundCass
            WOMatWfrRec          = Database_Services('ReadDataRow', 'WO_MAT_WFR', WOMatWfrKey)
            ReadyQ               = WOMatWfrRec<WO_MAT_WFR_IN_WFR_ID$>
            ConsumedQ            = WOMatWfrRec<WO_MAT_WFR_IN_PREV_WFR_ID$>
            ReadyQ<0, SlotNo>    = ''
            ConsumedQ<0, SlotNo> = InWfrID
            WOMatWfrRec<WO_MAT_WFR_IN_WFR_ID$>      = ReadyQ
            WOMatWfrRec<WO_MAT_WFR_IN_PREV_WFR_ID$> = ConsumedQ
            Database_Services('WriteDataRow', 'WO_MAT_WFR', WOMatWfrKey, WOMatWfrRec, True$, False$, True$)            
        Next InWfrID
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// GetInvActions
//
//
//----------------------------------------------------------------------------------------------------------------------
Service GetInvActions(StageID)
    
    If StageID NE '' then
        GaNStageInfo = Database_Services('ReadDataRow', 'APP_INFO', 'GAN_STAGES')
        GaNStageInfo = SRP_Array('Rotate', GaNStageInfo, @FM, @VM)
        StageIDs     = GaNStageInfo<1>
        InvActions   = GaNStageInfo<7>
        Locate StageID in StageIDs using @VM setting vPos then
            Response = InvActions<0, vPos>
            Convert @SVM to @VM in Response
        end
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// GetSigReq
//
//
//----------------------------------------------------------------------------------------------------------------------
Service GetSigReq(StageID)

    If StageID NE '' then
        GaNStageInfo = Database_Services('ReadDataRow', 'APP_INFO', 'GAN_STAGES')
        GaNStageInfo = SRP_Array('Rotate', GaNStageInfo, @FM, @VM)
        StageIDs     = GaNStageInfo<1>
        SigReqs      = GaNStageInfo<8>
        Locate StageID in StageIDs using @VM setting vPos then
            Response = SigReqs<0, vPos>
        end
    end
    If Response EQ '' then Response = False$
    
end service


//----------------------------------------------------------------------------------------------------------------------
// GetReqStages
//
//
//----------------------------------------------------------------------------------------------------------------------
Service GetReqStages()

    Response     = ''
    GaNStageInfo = Database_Services('ReadDataRow', 'APP_INFO', 'GAN_STAGES')
    GaNStageInfo = SRP_Array('Rotate', GaNStageInfo, @FM, @VM)
    StageIDs     = GaNStageInfo<1>
    StageReq     = GaNStageInfo<9>
    For each StageID in StageIDs using @VM setting vPos
        If StageReq<0, vPos> EQ True$ then Response<0, -1> = StageID
    Next StageID
    
end service


//----------------------------------------------------------------------------------------------------------------------
// AddStageToPSN
//
//
//----------------------------------------------------------------------------------------------------------------------
Service AddStageToPSN(PSNo, StageID)
    
    Response = False$
    If ( (PSNo NE '') and (StageID NE '') ) then
        PSRec = Database_Services('ReadDataRow', 'PROD_SPEC', PSNo)
        PRSStageKeys = PSRec<PROD_SPEC_PRS_STAGE_KEY$>
        PRSStageKeysPrime = SRP_Array('Rotate', PRSStageKeys, @VM, '*')
        PRSStages = PRSStageKeysPrime<0, 2>
        Locate StageID in PRSStages using @VM setting vPos else
            // Stage not yet in the PSN, so add it now
            ToolClasses = GaN_Services('GetToolClassIDs', StageID)
            InvActions  = GaN_Services('GetInvActions', StageID)
            SigReq      = GaN_Services('GetSigReq', StageID)
            PRSStageKey = PSNo:'*':StageID
            PRSStageRec = ''               
            PRSStageRec<PRS_STAGE_PROC_TOOL_CLASS$> = ToolClasses
            If InvActions NE '' then
                PRSStageRec<PRS_STAGE_PROC_INV_ACTION$> = InvActions            
                If SigReq EQ True$ then
                    For each InvAction in InvActions using @VM setting vPos
                        PRSStageRec<PRS_STAGE_PROC_INV_SIG_REQ$, vPos> = True$ 
                    Next InvAction
                end
            end
            Database_Services('WriteDataRow', 'PRS_STAGE', PRSStageKey, PRSStageRec, True$, False$, True$)
            If Error_Services('NoError') then Response = True$
        end
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// RemoveStageFromPSN
//
//
//----------------------------------------------------------------------------------------------------------------------
Service RemoveStageFromPSN(PSNo, StageID)
    
    Response = False$
    If ( (PSNo NE '') and (StageID NE '') ) then
        PRSStageKey = PSNo:'*':StageID
        If RowExists('PRS_STAGE', PRSStageKey) then
            Database_Services('DeleteDataRow', 'PRS_STAGE', PRSStageKey, True$, False$)
            If Error_Services('NoError') then Response = True$
        end
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// UpdateRetainedWafers
//
//
//----------------------------------------------------------------------------------------------------------------------
Service UpdateRetainedWafers()

    hSysLists   = Database_Services('GetTableHandle', 'SYSLISTS')
    Lock hSysLists, ServiceKeyID then
        UpdateFlag = Database_Services('ReadDataRow', 'APP_INFO', 'UPDATE_RETAINED_WAFERS_FLAG')
        If UpdateFlag EQ True$ then
            // Clear update flag
            UpdateFlag = False$
            Database_Services('WriteDataRow', 'APP_INFO', 'UPDATE_RETAINED_WAFERS_FLAG', UpdateFlag, True$, False$, True$)
            // Update wafer lists                        
            hDict       = Database_Services('GetTableHandle', 'DICT.RETAINED_WAFERS')
            If Error_Services('NoError') then
                Database_Services('WriteDataRow', 'APP_INFO', 'UPDATE_RETAINED_WAFERS_FLAG', False$, True$, False$, True$)
                WaferSizes = '150 mm 6 in' : @VM : '200 mm 8 in'
                WaferListKeys = 'RETAINED_WAFERS_6IN' : @VM : 'RETAINED_WAFERS_8IN'
                // Update 6 and 8 inch wafer lists
                For each WaferSize in WaferSizes using @VM setting SizePos
                    WaferListKey = WaferListKeys<0, SizePos>
                    HaveLock    = Database_Services('GetKeyIDLock', 'APP_INFO', WaferListKey, True$)
                    If HaveLock EQ True$ then
                        // OK to update
                        WaferList  = ''   
                        Query      = 'WAFER_SIZE' : @VM : WaferSize : @FM
                        KeyList    = ''
                        Option     = ''
                        Flag       = ''
                        Btree.Extract(Query, 'RETAINED_WAFERS', hDict, KeyList, Option, Flag)
                        NumKeys = DCount(KeyList, @VM)
                        If Get_Status(ErrCode) else
                            For each WfrID in keylist using @VM setting RowIndex
                                WfrRec          = Database_Services('ReadDataRow', 'RETAINED_WAFERS', WfrID)
                                WaferList<RowIndex,  1> = WfrRec<RETAINED_WAFERS.RUN_ID$>
                                WaferList<RowIndex,  2> = WfrRec<RETAINED_WAFERS.RECIPE$>
                                WaferList<RowIndex,  3> = WfrRec<RETAINED_WAFERS.POCKET$>
                                WaferList<RowIndex,  4> = WfrRec<RETAINED_WAFERS.SCRIBE$>
                                WaferList<RowIndex,  5> = WfrRec<RETAINED_WAFERS.GRADE$>
                                WaferList<RowIndex,  6> = WfrRec<RETAINED_WAFERS.PART_NO$>
                                WaferList<RowIndex,  7> = Field(WfrID, '*', 1)
                                WaferList<RowIndex,  8> = WfrRec<RETAINED_WAFERS.RETAIN_BOX$>
                                WaferList<RowIndex,  9> = WfrRec<RETAINED_WAFERS.RETAIN_SLOT$>
                                WaferList<RowIndex, 10> = WfrRec<RETAINED_WAFERS.LOCATION$>
                                WaferList<RowIndex, 11> = WfrRec<RETAINED_WAFERS.STATUS$>
                                WaferList<RowIndex, 12> = WfrRec<RETAINED_WAFERS.COMMENT$>
                                WaferList<RowIndex, 13> = WfrRec<RETAINED_WAFERS.RETAIN_SIG$>
                                WaferList<RowIndex, 14> = WfrRec<RETAINED_WAFERS.RETAIN_DT$>
                                WaferList<RowIndex, 15> = WfrRec<RETAINED_WAFERS.DESTROY_SIG$>
                                WaferList<RowIndex, 16> = WfrRec<RETAINED_WAFERS.DESTROY_DT$>
                                WaferList<RowIndex, 17> = WfrID
                            Next WfrID
                        end
                        Database_Services('WriteDataRow', 'APP_INFO', WaferListKey, WaferList, True$, False$, True$)
                        Database_Services('ReleaseKeyIDLock', 'APP_INFO', WaferListKey)
                    end
                Next WaferSize               
            end        
        end        
        Unlock hSysLists, ServiceKeyID else Null
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// UpdateRetainedWaferDetails
//
// Input:
//      WaferID     - [Required]
//      Location    - [Optional]
//      Status      - [Optional]
//      Comments    - [Optional]
//
// Output:
//      Response - True$ (1) if successful, False$ (0) otherwise
//
//----------------------------------------------------------------------------------------------------------------------
Service UpdateRetainedWaferDetails(InboundWfrID, Location, Status, Comments)
    
    Response = False$
    If InboundWfrID NE '' then
        WOWfrRec = Database_Services('ReadDataRow', 'WO_WFR', InboundWfrID)
        If Error_Services('NoError') then
            WOWfrRec<WO_WFR_RETAIN_LOC$>        = Location
            WOWfrRec<WO_WFR_RETAIN_STATUS$>     = Status
            WOWfrRec<WO_WFR_RETAIN_COMMENT$>    = Comments
            Database_Services('WriteDataRow', 'WO_WFR', InboundWfrID, WOWfrRec, True$, False$, True$)
            If Error_Services('NoError') then Response = True$
        end 
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// GetCurrPartParams
//
//
//----------------------------------------------------------------------------------------------------------------------
Service GetCurrPartParams(PartNo)

    Stages = Database_Services('ReadDataRow', 'APP_INFO', 'GAN_IQS_STAGES')
    Swap 'JV-XRD' with 'JV_XRD' in Stages
    ParameterList  = ''

    Query  = "SELECT pr.Process_Name, pr.IQS_Name, pp.Parameter "                       
    Query := "FROM [ControlPlans].[dbo].[Part_Parameter] pp "                           
    Query := "JOIN [ControlPlans].[dbo].[Parameter] pr on pp.Parameter = pr.Parameter " 
    Query := "WHERE pp.Part = '":PartNo:"' AND ("
    For each Stage in Stages using @VM
        Query := "pr.Process_Name = '":Stage:"' OR "                                              
    Next Stage
    Query[-1, -4] = ""
    Query := ") ORDER BY pr.Process_Name, pr.IQS_Name"

    Response = SQL_Services('PostSQLRequest', 'IQSDMS1', Query)
    RowCount = 0

    If Response NE 0 then                
        For each Row in Response using @RM setting RowPos
            RowCount += 1
            ProcName  = Row<1>
            MetName   = Row<2> 
            ParamName = Row<3>
            Locate ProcName in Stages using @VM setting fPos then
                vPos = DCount(ParameterList<fPos>, @VM)
                vPos += 1
                ParameterList<fPos, vPos, 1> = MetName
                ParameterList<fPos, vPos, 2> = ParamName
            end
        Next Row
    end
    
*    Response    = ParameterList
    // Copy UV to UV_PST
    ParameterList<7> = ParameterList<3>
    UVData      = ParameterList<7>
    For each Row in UVData using @VM setting vPos
        MetName              = ParameterList<7, vPos, 1>
        MetName              = MetName:' Post'
        ParameterList<7, vPos, 1> = MetName
    Next Row
    // Copy CAN to CAN_PST
    ParameterList<8> = ParameterList<6>
    CANData     = ParameterList<8>
    For each Row in CANData using @VM setting vPos
        MetName              = ParameterList<8, vPos, 1>
        MetName              = MetName:' Post'
        ParameterList<8, vPos, 1> = MetName
    Next Row
    // Manually create BV100% parameter
    ParameterList<17, 1, 1> = '100% BV Failure %'
    ParameterList<17, 1, 2> = '100% BV Failure %'
    
    // Hard code Plato limits for now
*    ParameterList<5, 1, 1> = 'Epi Thickness Mean Plato'
*    ParameterList<5, 1, 2> = 'Epi Thickness Mean Plato'
*    ParameterList<5, 2, 1> = 'Plato Thickness Std Dev %'
*    ParameterList<5, 2, 2> = 'Plato Thickness Std Dev %'
    
*    // Get GROWTH parameters
*    Query     = 'SELECT DISTINCT pr.Process_Name, pr.IQS_Name, pp.Parameter '
*    Query    := 'From [ControlPlans].[dbo].[Part_Parameter] pp '
*    Query    := 'JOIN [ControlPlans].[dbo].[Parameter] pr on pp.Parameter  = pr.Parameter '
*    Query    := "WHERE pr.Process_Name  = 'Epi' AND pp.Part = '":PartNo:"'"
*    Query    := 'ORDER BY pr.IQS_Name'
*    Response  = SQL_Services('PostSQLRequest', 'IQSDMS1', Query)
* 
*    If Response NE 0 then
*        For each Row in Response using @RM setting RowPos
*            RowCount += 1
*            ProcName  = Row<1>
*            MetName   = Row<2> 
*            ParamName = Row<3>
*            Locate ProcName in Stages using @VM setting fPos then
*                vPos = DCount(ParameterList<fPos>, @VM)
*                vPos += 1
*                ParameterList<fPos, vPos, 1> = MetName
*                ParameterList<fPos, vPos, 2> = ParamName
*            end
*        Next Row
*    end
*    
*    // Get TEMP parameters
*    Query     = 'SELECT DISTINCT pr.Process_Name, pr.IQS_Name, pp.Parameter '
*    Query    := 'From [ControlPlans].[dbo].[Part_Parameter] pp '
*    Query    := 'JOIN [ControlPlans].[dbo].[Parameter] pr on pp.Parameter  = pr.Parameter '
*    Query    := "WHERE pr.Process_Name  = 'Temperature' "
*    Query    := 'ORDER BY pr.IQS_Name'
*    Response  = SQL_Services('PostSQLRequest', 'IQSDMS1', Query)
*    If Response NE 0 then
*        For each Row in Response using @RM setting RowPos
*            RowCount += 1
*            ProcName  = Row<1>
*            MetName   = Row<2> 
*            ParamName = Row<3>
*            Locate ProcName in Stages using @VM setting fPos then
*                vPos = DCount(ParameterList<fPos>, @VM)
*                vPos += 1
*                ParameterList<fPos, vPos, 1> = MetName
*                ParameterList<fPos, vPos, 2> = ParamName
*            end
*        Next Row
*    end
    Response = ParameterList

end service


//----------------------------------------------------------------------------------------------------------------------
// GetCurrLimits
//
// Input  - PartNo
// Output - LSL, Target, and USL from the Control Plan SQL DB.
//----------------------------------------------------------------------------------------------------------------------
Service GetCurrLimits(PartNo)

    Query = "SELECT [Parameter] "                           |
          : ",[LSL] "                                       |
          : ",[Target] "                                    |
          : ",[USL] "                                       |
          : "FROM [ControlPlans].[dbo].[Part_Parameter] "   |
          : "WHERE [Part] = '":PartNo:"' "                  |
          : "ORDER BY [Parameter]"
    Response = SQL_Services('PostSQLRequest', 'IQSDMS1', Query)
    Convert @FM to @VM in Response
    Convert @RM to @FM in Response

    Response        = SRP_Array('Rotate', Response, @FM, @VM)

    // Hard code 100% BV Failure % limits
    Response<1, -1> = '100% BV Failure %'
    Response<2, -1> = ''
    Response<3, -1> = ''
    Response<4, -1> = 10
    
    // Hard code Plato limits
    Response<1, -1> = 'Epi Thickness Mean Plato'
    Response<2, -1> = 0   ; // LSL
    Response<3, -1> = 1.5 ; // Target
    Response<4, -1> = 3   ; // USL
    
    // Hard code Plato limits
    Response<1, -1> = 'Plato Thickness Std Dev %'
    Response<2, -1> = -1 ; // LSL
    Response<3, -1> = 0 ; // Target
    Response<4, -1> = 1 ; // USL

end service


//----------------------------------------------------------------------------------------------------------------------
// GetMaxDecimals
//
// Input  - @VM delimited array of values containing decimals
// Output - Maximum number of decimal places found between all values in the array.
//----------------------------------------------------------------------------------------------------------------------
Service GetMaxDecimals(Array)
     
    MaxDecimals = 0
    If Array NE '' then
        For each Value in Array using @VM
            DecimalFound = Index(Value, '.', 1)
            If DecimalFound then
                NumDecimalPlaces = Len(Value[-1, 'B.'])
                If NumDecimalPlaces GT MaxDecimals then MaxDecimals = NumDecimalPlaces
            end
        Next Value
    end
    Response = MaxDecimals
    
end service


//----------------------------------------------------------------------------------------------------------------------																													
// ClearRetainInfo
//
// Input:
//      InboundWfrID - [Required]
//
// Output:
//      Response - True$ (1) if successful, False$ (0) otherwise.
//
//----------------------------------------------------------------------------------------------------------------------
Service ClearRetainInfo(InboundWfrID)
     
    Response = False$
    If InboundWfrID NE '' then
        // InboundWfrID may have been passed in with '.' instead of '*'
        Convert '.' to '*' in InboundWfrID
        WOWfrRec = Database_Services('ReadDataRow', 'WO_WFR', InboundWfrID)
        If Error_Services('NoError') then
            RetainBox  = WOWfrRec<WO_WFR_RETAIN_BOX$>
            RetainSlot = WOWfrRec<WO_WFR_RETAIN_SLOT$>
            If ( (RetainBox NE '') and (RetainSlot NE '') ) then
                WOWfrRec<WO_WFR_RETAIN_BOX$>  = ''
                WOWfrRec<WO_WFR_RETAIN_SLOT$> = ''
                Database_Services('WriteDataRow', 'WO_WFR', InboundWfrID, WOWfrRec, True$, False$, True$)
                If Error_Services('NoError') then
                    RDSNo = Xlate('WO_WFR', InboundWfrID, 'RDS_NO', 'X')
                    If RDSNo NE '' then
                        RetainStageKey = RDSNo:'*RETAIN*':InboundWfrID
                        RunStageWfrRec = Database_Services('ReadDataRow', 'RUN_STAGE_WFR', RetainStageKey)
                        RunStageWfrRec<RUN_STAGE_WFR_COMP_BY$>  = ''
                        RunStageWfrRec<RUN_STAGE_WFR_COMP_DTM$> = ''
                        RunStageWfrRec<RUN_STAGE_WFR_STATUS$>   = 'INIT'
                        Database_Services('WriteDataRow', 'RUN_STAGE_WFR', RetainKey, RunStageWfrRec, True$, False$, True$)
                        // Move wafer into the DISP queue, if it isn't already there
                        CurrQ = GaN_Services('GetWfrQueue', InboundWfrID)
                        DispQ = GaN_Services('GetLocQueueID', 'DISP')
                        If CurrQ NE DispQ then
                            Gan_Services('RemoveWfrFromWIP', WfrID)
                            GaN_Services('MoveWfrToQueue', InboundWfrID, DispQ)
                        end
                        ReactRunRec = Database_Services('ReadDataRow', 'REACT_RUN', RDSNo)
                        InWfrIDs    = ReactRunRec<REACT_RUN_IN_WFR_ID$>
                        Locate InboundWfrID in InWfrIDs using @VM setting vPos then
                            ReactRunRec<REACT_RUN_GAN_RET_WFR$, vPos> = False$
                            Database_Services('WriteDataRow', 'REACT_RUN', RDSNo, ReactRunRec, True$, False$, True$)
                        end
                        Response = True$
                    end
                end
            end
        end
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// RetainWafer
//
// Input:
//      WaferID  - [Required]
//
// Output:
//      Response - True$ (1) if successful, False$ (0) otherwise
//
//----------------------------------------------------------------------------------------------------------------------
Service RetainWafer(InboundWfrID)

    Response = False$
    If InboundWfrID NE '' then
        WfrSize = Xlate('WO_WFR', InboundWfrID, 'WFR_SIZE', 'X')
        WOWfrRec = Database_Services('ReadDataRow', 'WO_WFR', InboundWfrID)
        If Error_Services('NoError') then            
            OrigRetainBox  = WOWfrRec<WO_WFR_RETAIN_BOX$> 
            OrigRetainSlot = WOWfrRec<WO_WFR_RETAIN_SLOT$>
            If ( (OrigRetainBox EQ '') and (OrigRetainSlot EQ '') ) then
                // Wafer not yet retained, so proceed            
                RetainKey = Gan_Services('GetNextRetainKey', WfrSize)
                If RetainKey NE '' then              
                    RetainBox  = RetainKey<1>
                    RetainSlot = RetainKey<2>
                    WOWfrRec<WO_WFR_RETAIN_BOX$>  = RetainBox
                    WOWfrRec<WO_WFR_RETAIN_SLOT$> = RetainSlot                                 
                    Database_Services('WriteDataRow', 'WO_WFR', InboundWfrID, WOWfrRec, True$, False$, True$)
                    If Error_Services('NoError') then
                        // Move wafer to RETAIN queue
                        // Move wafer into the RETAIN queue, if it isn't already there
                        CurrQ   = GaN_Services('GetWfrQueue', InboundWfrID)
                        RetainQ = GaN_Services('GetLocQueueID', 'RETAIN')
                        If CurrQ NE RetainQ then
                            GaN_Services('RemoveWfrFromWIP', WfrID)
                            GaN_Services('MoveWfrToQueue', InboundWfrID, RetainQ)
                        end                    
                        If Error_Services('NoError') then
                            RDSNo = Xlate('WO_WFR', InboundWfrID, 'RDS_NO', 'X')
                            If RDSNo NE '' then
                                Convert '*' to '.' in InboundWfrID
                                RetainStageKey = RDSNo:'*RETAIN*':InboundWfrID
                                RunStageWfrRec = Database_Services('ReadDataRow', 'RUN_STAGE_WFR', RetainStageKey)
                                StageStatus    = RunStageWfrRec<RUN_STAGE_WFR_STATUS$>
                                If StageStatus NE 'COMP' then
                                    RunStageWfrRec<RUN_STAGE_WFR_COMP_BY$>  = 'AUTO'
                                    RunStageWfrRec<RUN_STAGE_WFR_COMP_DTM$> = Datetime()
                                    RunStageWfrRec<RUN_STAGE_WFR_STATUS$>   = 'COMP'
                                    Database_Services('WriteDataRow', 'RUN_STAGE_WFR', RetainStageKey, RunStageWfrRec, True$, False$, True$)
                                end
                                DispStageKey = RDSNo:'*DISP*':InboundWfrID
                                RunStageWfrRec = Database_Services('ReadDataRow', 'RUN_STAGE_WFR', DispStageKey)
                                StageStatus    = RunStageWfrRec<RUN_STAGE_WFR_STATUS$>
                                If StageStatus NE 'COMP' then
                                    RunStageWfrRec<RUN_STAGE_WFR_COMP_BY$>  = 'AUTO'
                                    RunStageWfrRec<RUN_STAGE_WFR_COMP_DTM$> = Datetime()
                                    RunStageWfrRec<RUN_STAGE_WFR_STATUS$>   = 'COMP'
                                    Database_Services('WriteDataRow', 'RUN_STAGE_WFR', DispStageKey, RunStageWfrRec, True$, False$, True$)
                                end
                                ReactRunRec = Database_Services('ReadDataRow', 'REACT_RUN', RDSNo)
                                InWfrIDs    = ReactRunRec<REACT_RUN_IN_WFR_ID$>
                                Convert '.' to '*' in InboundWfrID
                                Locate InboundWfrID in InWfrIDs using @VM setting vPos then
                                    // This flag indicates the wafer is a retained wafer.
                                    // This write to the REACT_RUN record will also trigger an MFS to copy the
                                    // recipe and part number to the RETAINED_WAFERS record.
                                    ReactRunRec<REACT_RUN_GAN_RET_WFR$, vPos> = True$
                                    Database_Services('WriteDataRow', 'REACT_RUN', RDSNo, ReactRunRec, True$, False$, True$)
                                end
                            end
                            If Error_Services('NoError') then Response = True$
                        end
                    end
                end else
                    Error_Services('Add', 'Error retrieving retain key in the ':Service:' service.')
                end
            end
        end
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// SignRetainedWafer
//
// Input:
//      WaferID  - [Required]
//
// Output:
//      Response - True$ (1) if successful, False$ (0) otherwise
//
//----------------------------------------------------------------------------------------------------------------------
Service SignRetainedWafer(InboundWfrID)
    
    Response = False$
    If InboundWfrID NE '' then
        WOWfrRec = Database_Services('ReadDataRow', 'WO_WFR', InboundWfrID)
        If Error_Services('NoError') then
            WOWfrRec<WO_WFR_RETAIN_SIG$>  = @User4
            WOWfrRec<WO_WFR_RETAIN_DT$>   = Date()
            WOWfrRec<WO_WFR_RETAIN_LOC$>  = 'Cleanroom'                                  
            Database_Services('WriteDataRow', 'WO_WFR', InboundWfrID, WOWfrRec, True$, False$, True$)
            If Error_Services('NoError') then                
                Disposition_Services('AutoDispositionWfr', InboundWfrID)
                If Error_Services('NoError') then Response = True$
            end
        end 
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// SignDestroyedWafer
//
// Input:
//      WaferID  - [Required]
//
// Output:
//      Response - True$ (1) if successful, False$ (0) otherwise
//
//----------------------------------------------------------------------------------------------------------------------
Service SignDestroyedWafer(InboundWfrID)
    
    Response = False$
    If InboundWfrID NE '' then
        WOWfrRec = Database_Services('ReadDataRow', 'WO_WFR', InboundWfrID)
        If Error_Services('NoError') then
            WOWfrRec<WO_WFR_DESTROY_SIG$>   = @User4
            WOWfrRec<WO_WFR_DESTROY_DT$>    = Date()
            WOWfrRec<WO_WFR_RETAIN_LOC$>    = 'Wafer Destroyed'
            Database_Services('WriteDataRow', 'WO_WFR', InboundWfrID, WOWfrRec, True$, False$, True$)
            If Error_Services('NoError') then Response = True$
        end 
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// GetNextRetainKey
//
// Input:
//      WfrSize  - [Required]
//
// Output:
//      Response - Next retain key for that wafer size.
//
//----------------------------------------------------------------------------------------------------------------------
Service GetNextRetainKey(WfrSize)
    
    Response = ''
    If WfrSize NE '' then
        Begin Case
            Case WfrSize EQ '150 mm 6 in'
                RetainKeyID = 'RETAIN_KEY_6IN'
            Case WfrSize EQ '200 mm 8 in'
                RetainKeyID = 'RETAIN_KEY_8IN'
            Case Otherwise$
                Error_Services('Add', 'Null or incompatible wafer size in ':Service:' service.')
        End Case
        If Error_Services('NoError') then    
            CurrRetainKey  = Database_Services('ReadDataRow', 'APP_INFO', RetainKeyID)
            CurrRetainBox  = CurrRetainKey<1>
            CurrRetainSlot = CurrRetainKey<2>
            // Update Retain Key (i.e. Box and Slot)
            NewRetainBox = CurrRetainBox
            If CurrRetainSlot GT 1 then
                NewRetainSlot = CurrRetainSlot - 1
            end else
                NewRetainSlot      = 25
                NewBoxIndex        = CurrRetainBox[6, 3]
                NewBoxIndex        += 1
                NewBoxIndex        = Fmt(NewBoxIndex,"R(0)#3")                
                NewRetainBox[6, 3] = NewBoxIndex
            end
            NewRetainKey    = ''
            NewRetainKey<1> = NewRetainBox
            NewRetainKey<2> = NewRetainSlot
            Database_Services('WriteDataRow', 'APP_INFO', RetainKeyID, NewRetainKey, True$, False$, True$)
            If Error_Services('NoError') then Response = CurrRetainKey
        end
    end
        
end service


//----------------------------------------------------------------------------------------------------------------------
// GetAvailableTools
//
// Input:
//      StageID  - [Required]
//
// Output:
//      Response - All tools for a given stage that are currently running in PROD mode.
//
//----------------------------------------------------------------------------------------------------------------------
Service GetAvailableTools(StageID)

    Response = ''
    If StageID NE '' then
        ToolClassIDs = GaN_Services('GetToolClassIDs', StageID)
        TempArray    = ''
        For each ToolClassID in ToolClassIDs using @SVM setting svPos                    
            ToolIDs   = Xlate('TOOL_CLASS', ToolClassID, 'TOOL', 'X')
            ProdTools = ''
            For each ToolID in ToolIDs using @VM setting vPos
                CurrMode    = Xlate('TOOL', ToolID, 'CURR_MODE', 'X')
                If CurrMode EQ 'PROD' then ProdTools<0, -1> = ToolID
            Next ToolID
            TempArray = SRP_Array('Join', TempArray, ProdTools, 'OR', @VM)                               
        Next ToolClassID
        Response = TempArray
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// GetWaferStatusList
//
// Input:
//      RDSNo  - [Required]
//
// Output:
//      Response - A list of wafers in a given RDS and their current status.
//
//----------------------------------------------------------------------------------------------------------------------
Service GetWaferStatusList(RDSNo)
    
    Response = ''
    If RDSNo NE '' then
        WfrStatusList = ''
        InWfrIDs    = Xlate('REACT_RUN', RDSNo, 'IN_WFR_ID', 'X')
        For each WfrID in InWfrIDs using @VM setting WfrIndex
            WfrStatusList<WfrIndex, 1> = WfrID
            WfrStatusList<WfrIndex, 2> = Xlate('WO_WFR', WfrID, 'STATUS', 'X')
        Next WfrID
        Response = WfrStatusList
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// AbortWafer
//
// Input:
//      WfrID  - [Required]
//
// Output:
//      Response - True$ (1) if successful, False$ (0) otherwise.
//
//----------------------------------------------------------------------------------------------------------------------
Service AbortWafer(WfrID)
    
    Response = False$
    If WfrID NE '' then
        // WfrID may have been passed in with . instead of *
        Convert '.' to '*' in WfrID
        WOWfrRec    = Database_Services('ReadDataRow', 'WO_WFR', WfrID)        
        RDSNo       = Xlate('WO_WFR', WfrID, 'RDS_NO', 'X')
        WfrStages   = Xlate('REACT_RUN', RDSNo, 'WFR_STAGE', 'X')
        Locate 'WARP' in WfrStages using @VM setting StageIndex then
            // Skip all remaining stages, mark wafer as dispositioned at the SPLIT stage            
            Loop
                StageIndex += 1  
                StageID = WfrStages<0, StageIndex>               
            Until StageID EQ 'SPLIT'
                GaN_Services('SkipWaferStage', RDSNo, WfrID, StageID)            
            Repeat
            GaN_Services('DispositionWfr', RDSNo, WfrID, True$)
        end
        // Set critical failure flag (AutoDisposition will set the grades based on this flag)
        WOWfrRec<WO_WFR_CRITICAL_FAILURE$> = True$
        Database_Services('WriteDataRow', 'WO_WFR', WfrID, WOWfrRec, True$, False$, True$)      
        If Error_Services('NoError') then Response = True$        
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// RestoreWafer
//
// Input:
//      WfrID  - [Required]
//
// Output:
//      Response - True$ (1) if successful, False$ (0) otherwise.
//
//----------------------------------------------------------------------------------------------------------------------
Service RestoreWafer(WfrID)

    Response = False$
    If WfrID NE '' then
        // WfrID may have been passed in with . instead of *
        Convert '.' to '*' in WfrID
        RDSNo       = Xlate('WO_WFR', WfrID, 'RDS_NO', 'X')
        GaN_Services('WithdrawWfr', RDSNo, WfrID)
        WOWfrRec    = Database_Services('ReadDataRow', 'WO_WFR', WfrID)                
        WfrStages   = Xlate('REACT_RUN', RDSNo, 'WFR_STAGE', 'X')
        Convert '*' to '.' in WfrID
        For each StageID in WfrStages using @VM setting StageIndex
        Until StageID EQ 'SPLIT'
            RunStageWfrKey = RDSNo:'*':StageID:'*':WfrID
            StageStatus    = Xlate('RUN_STAGE_WFR', RunStageWfrKey, 'STATUS', 'X')
            If StageStatus EQ 'SKIP' then GaN_Services('UnskipWaferStage', RDSNo, WfrID, StageID)
        Next StageID
        Convert '.' to '*' in WfrID    
        // Set critical failure flag (AutoDisposition will set the grades based on this flag)
        WOWfrRec<WO_WFR_CRITICAL_FAILURE$> = False$
        Database_Services('WriteDataRow', 'WO_WFR', WfrID, WOWfrRec, True$, False$, True$)      
        If Error_Services('NoError') then Response = True$        
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// PostDispositionRequest
//
// Input:
//      RDSNo  - [Required]
//
//----------------------------------------------------------------------------------------------------------------------
Service PostDispositionRequest(RDSNo)
    
    Response = ''
    If RDSNo NE '' then
        RequestDate     = Date()
        RequestTime     = Time()
        RequestKeyID    = RDSNo:'*':RequestDate :'*':RequestTime
        RequestRow      = ''
        Database_Services('WriteDataRow', 'DISPOSITION_REQUESTS', RequestKeyID, RequestRow, False$, False$, False$)
        If Error_Services('NoError') then
            TimeExpired = False$
            Start       = GetTickCount()
            Loop
                RequestRow      = Database_Services('ReadDataRow', 'DISPOSITION_REQUESTS', RequestKeyID)
                ResponseDate    = RequestRow<DISPOSITION_REQUESTS.RESPONSE_DATE$>
                Response        = RequestRow<DISPOSITION_REQUESTS.RESPONSE$>
                // Time will expire after 30 seconds.
                If GetTickCount() - Start GE 60000 then TimeExpired = True$
            Until (ResponseDate NE '') OR TimeExpired
                Sleepery(10)
                WinYield()
                Yield();Yield();Yield();Yield();Yield();Yield();Yield();Yield()
            Repeat
            If TimeExpired then
                Error_Services('Add', 'Disposition request timed out in the ' : Service : ' service.')
            end
            Database_Services('DeleteDataRow', 'DISPOSITION_REQUESTS', RequestKeyID, True$)
        end
    end else
        Error_Services('Add', 'Null parameter passed into service call. All parameters are required.')
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// ProcessDispositionRequests
//
//----------------------------------------------------------------------------------------------------------------------
Service ProcessDispositionRequests()
    
    Tablename = 'DISPOSITION_REQUESTS'
    hValidationRequests = Database_Services('GetTableHandle', Tablename)
    If Error_Services('NoError') then
        Sentence    = 'SELECT ':Tablename:' WITH RESPONSE_DATE EQ "" BY REQUEST_DATE BY REQUEST_TIME'
        Set_Status(0)
        RList(Sentence, TARGET_ACTIVELIST$, '', '', '')
        EOF         = False$
        Loop
            ReadNext RequestKeyID else EOF = True$           
        Until EOF
            Lock hValidationRequests, RequestKeyID then                
                RequestRow = Database_Services('ReadDataRow', 'DISPOSITION_REQUESTS', RequestKeyID)
                RDSNo      = Field(RequestKeyID, '*', 1)
                GaN_Services('ProcessDispositionRequest', RDSNo)
                If Error_Services('NoError') then
                    RequestRow<DISPOSITION_REQUESTS.RESPONSE$> = True$
                end else
                    RequestRow<DISPOSITION_REQUESTS.RESPONSE$> = Error_Services('GetMessage')
                end                
                RequestRow<DISPOSITION_REQUESTS.RESPONSE_DATE$> = Date()
                RequestRow<DISPOSITION_REQUESTS.RESPONSE_TIME$> = Time()
                Database_Services('WriteDataRow', 'DISPOSITION_REQUESTS', RequestKeyID, RequestRow, True$, False$, True$)
                Unlock hValidationRequests, RequestKeyID else
                    Error_Services('Add', 'Error unlocking ':RequestKeyID:', ':Tablename:' within ':Service)
                end
            end
        Repeat
    end else
        ErrorMsg = Error_Services('GetMessage')
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// ProcessDispositionRequest
//
// Input:
//      RDSNo  - [Required]
//
//----------------------------------------------------------------------------------------------------------------------
Service ProcessDispositionRequest(RDSNo)
    
    If RDSNo NE '' then
        ReactRunRec     = Database_Services('ReadDataRow', 'REACT_RUN', RDSNo)
        If Error_Services('NoError') then
            OrigReactRunRec = ReactRunRec
            Reactor         = ReactRunRec<REACT_RUN_REACTOR$>
            Scribes         = Xlate('REACT_RUN', RDSNo, 'WFR_SCRIBES', 'X')
            NumWfrs         = DCount(Scribes, @VM)
            GanRunID        = ReactRunRec<REACT_RUN_GAN_RUN_ID$>
            GaNRecipe       = ReactRunRec<REACT_RUN_GAN_RECIPE$>
            WfrIDs          = ReactRunRec<REACT_RUN_IN_WFR_ID$>
            CassDispComp    = ReactRunRec<REACT_RUN_DISP_COMPLETE$>
            WONo            = ReactRunRec<REACT_RUN_WO_NO$>
            WaferTrackPart  = Xlate('WO_LOG', WONo, 'WAFER_TRACK_PART', 'X')
            If CassDispComp NE True$ then
                ReportFound      = False$
                // Search for GaN Disposition Report Excel Spreadsheet --------------------
                DispFilename     = GanRunID:'_':WaferTrackPart:'_Disposition_Report.xlsm'
                ReportPath       = '\\messv02ecc1.ec.local\EC_Characterization_GaN\Disposition Reports\'
                InitDir ReportPath:'\*.*'
                RootFileList     = DirList()
                DispFilePath     = ReportPath:'\':DispFilename
                If Indexc(RootFileList, DispFileName, 1) then
                    ReportFound = True$
                end else
                    // Report not found in root "working" directory, so search the archives
                    ArchivePath = ReportPath:'\R':Reactor:' Reports\*.*'
                    InitDir ArchivePath
                    ArchiveFileList = DirList()
                    If Indexc(ArchiveFileList, DispFileName, 1) then
                        ReportFound = True$
                        DispFilePath = ReportPath:'\R':Reactor:' Reports\':DispFilename
                    end else
                        // Disposition report not found. Set status line to inform user
                        Set_Property(@Window:'.STATUS_LINE', 'BACKCOLOR', YELLOW$)
                        Set_Property(@Window:'.STATUS_LINE', 'TEXT', 'Disposition report "':DispFileName:'" not found!')
                    end
                end 
                If ReportFound EQ True$ then
                    Set_Property(@Window:'.STATUS_LINE', 'BACKCOLOR', GREEN$)
                    DispExcelHandle  = Excel_Services('OpenDocument', DispFilePath)
                    DispWorksheet    = 'Section E-Lot Summary Report'  
                    ExcelScribes     = ''
                    ExcelGrades      = ''
                    ExcelShipFlags   = ''
                    ExcelRetainFlags = ''
                    ExcelReasons     = ''
                    ExcelNCRReqFlags = ''
                    For WfrIndex = 1 to NumWfrs            
                        WfrID                     = WfrIDs<0, WfrIndex>
                        WOWfrRec                  = Database_Services('ReadDataRow', 'WO_WFR', WfrID)
                        FormattedWfrID            = WfrID
                        Convert '*' to '.' in FormattedWfrID   
                        CellRow                   = WfrIndex + 6
                        ExcelScribe               = Excel_Services('GetCellValue', DispExcelHandle, DispWorksheet, 'D', CellRow)
                        ExcelScribes<0, WfrIndex> = ExcelScribe
                        MetComp  = Gan_Services('GetMetrologyStatus', WfrID)
                        DispComp = Gan_Services('GetDispStatus', WfrID)
                        
                        If ( (MetComp EQ True$) or (DispComp EQ True$) ) then
                            ExcelGrade                    = Excel_Services('GetCellValue', DispExcelHandle, DispWorksheet, 'O', CellRow)
                            ExcelReason                   = Excel_Services('GetCellValue', DispExcelHandle, DispWorksheet, 'P', CellRow)
                            ExcelRetainFlag               = Excel_Services('GetCellValue', DispExcelHandle, DispWorksheet, 'R', CellRow)
                            ExcelGrades<0, WfrIndex>      = ExcelGrade
                            ExcelShipFlag                 = (ExcelRetainFlag _EQC 'IFX-VIH')
                            ExcelShipFlags<0, WfrIndex>   = ExcelShipFlag
                            ExcelRetainFlag               = (ExcelRetainFlag _EQC 'Retained')
                            ExcelRetainFlags<0, WfrIndex> = ExcelRetainFlag
                            ExcelReasons<0, WfrIndex>     = ExcelReason
                            ExcelNCRReqFlags<0, WfrIndex> = (ExcelReason _EQC 'Lot Aborted') or (ExcelReason _EQC 'Non-rotation') or (ExcelReason _EQC 'Broken')
                            WOWfrRec   = Database_Services('ReadDataRow', 'WO_WFR', WfrID)
                            RetainSlot = WOWfrRec<WO_WFR_RETAIN_SLOT$>
                            RetainBox  = WOWfrRec<WO_WFR_RETAIN_BOX$>
                            WOWfrGrade = WOWfrRec<WO_WFR_GRADE$>
                            If WOWfrGrade NE ExcelGrade then
                                WOWfrRec<WO_WFR_GRADE$> = ExcelGrade
                                Database_Services('WriteDataRow', 'WO_WFR', WfrID, WOWfrRec, True$, False$, True$)
                            end
                            OrigShipFlag   = ReactRunRec<REACT_RUN_CARR_WFR_SHIP$, WfrIndex>

                            If ( (ExcelRetainFlag EQ False$) and (RetainBox NE '') and (RetainSlot NE '') ) then
                                // Reset RETAIN stage back to INIT because the disposition report has been updated
                                GaN_Services('ClearRetainInfo', WfrID)
                            end
                            If ( (ExcelRetainFlag EQ True$ ) and (RetainBox EQ '') and (RetainSlot EQ '') ) then
                                GaN_Services('RetainWafer', WfrID)
                            end

                            If ( (ExcelShipFlag EQ False$) and (OrigShipFlag EQ True$) ) then
                                // Reset G_PACK stage back to INIT because the disposition report has been updated
                                GPackKey = RDSNo:'*G_PACK*':FormattedWfrID
                                RunStageWfrRec = Database_Services('ReadDataRow', 'RUN_STAGE_WFR', GPackKey)
                                RunStageWfrRec<RUN_STAGE_WFR_COMP_BY$>  = ''
                                RunStageWfrRec<RUN_STAGE_WFR_COMP_DTM$> = ''
                                RunStageWfrRec<RUN_STAGE_WFR_STATUS$>   = 'INIT'
                                Database_Services('WriteDataRow', 'RUN_STAGE_WFR', GPackKey, RunStageWfrRec, True$, False$, True$)
                            end
                        end
                        
                    Next WfrIndex
                    ReactRunRec<REACT_RUN_CARR_WFR_SHIP$>   = ExcelShipFlags
                    ReactRunRec<REACT_RUN_GAN_RET_WFR$>     = ExcelRetainFlags
                    ReactRunRec<REACT_RUN_NCR_REQ$>         = ExcelNCRReqFlags
                    ReactRunRec<REACT_RUN_DISP_REASON$>     = ExcelReasons
                    InternalFlags     = ReactRunRec<REACT_RUN_INTERNAL$>
                    ExternalFlags     = ReactRunRec<REACT_RUN_EXTERNAL$>
                    For WfrIndex = 1 to NumWfrs
                        RetainFlag = ExcelRetainFlags<0, WfrIndex>
                        ShipFlag   = ExcelShipFlags<0, WfrIndex>
                        If (RetainFlag EQ True$) or (ShipFlag EQ True$) then
                            InternalFlags<0, WfrIndex> = False$
                            ExternalFlags<0, WfrIndex> = False$
                        end
                    Next WfrIndex
                    ReactRunRec<REACT_RUN_INTERNAL$> = InternalFlags 
                    ReactRunRec<REACT_RUN_EXTERNAL$> = ExternalFlags
                    If OrigReactRunRec NE ReactRunRec then
                        Database_Services('WriteDataRow', 'REACT_RUN', RDSNo, ReactRunRec, True$, False$, True$)
                    end
                    Excel_Services('CloseDocument', DispExcelHandle)           
                end else
                    Error_Services('Add', 'Dispostion report not found!')
                end
            end else
                Error_Services('Add', 'Cannot process disposition report due to run being closed.')
            end
        end
    end else
        Error_Services('Add', 'Null RDSNo passed into service ':Service:'.')
    end

end service


Service VerifyWaferScribes(RDSNo, ToolID)

    ErrorMsg = ''
    If ( (RDSNo NE '') and (ToolID NE '') ) then
        GaNRunID     = Xlate('REACT_RUN', RDSNo, 'GAN_RUN_ID', 'X')
        WfrTrackPart = Xlate('REACT_RUN', RDSNo, 'WFR_TRACK_PART', 'X')
        IQSToolID    = Xlate('TOOL', ToolID, 'IQS_ID', 'X')
        Done         = False$
        If ( (GaNRunID NE '') and (WfrTrackPart NE '') and (IQSToolID NE '') ) then
            Begin Case
                Case ( (ToolID EQ 'CAN02') or (ToolID EQ 'PLATO_02') )
                    ColName = 'Wafer Pocket'
                Case ToolID EQ 'XRD04'
                    ColName = 'Wafer ID'
                Case Otherwise$
                    Done = True$
            End Case
            If Done EQ False$ then
                
                Query  = "select [":ColName:"], [Wafer Scribe] "
                Query := "from ( "
                Query := "select sd_sgrp, sd_tsno, jd_name, pl_name, pd_name, "
                Query := "max(case when dg_name = '":ColName:"' then dd_name end) [":ColName:"], "
                Query := "max(case when dg_name = 'Wafer Scribe' then dd_name end) [Wafer Scribe] "
                Query := "from ( "
                Query := "select sd.f_sgrp sd_sgrp, jd.f_name jd_name, pl.f_name pl_name, pd.f_name pd_name, sd.f_tsno sd_tsno, dg.f_name dg_name, dd.f_name dd_name "
                Query := "from [IRMNSPC].[dbo].[SGRP_DSC] sd "
                Query := "join [IRMNSPC].[dbo].[SGRP_EXT] se "
                Query := "on sd.f_sgrp = se.f_sgrp "
                Query := "join [IRMNSPC].[dbo].[JOB_DAT] jd "
                Query := "on se.f_job = jd.f_job "
                Query := "join [IRMNSPC].[dbo].[PART_LOT] pl "
                Query := "on se.f_lot = pl.f_lot "
                Query := "join [IRMNSPC].[dbo].[PART_DAT] pd "
                Query := "on se.f_part = pd.f_part "
                Query := "join [IRMNSPC].[dbo].[DESC_DAT] dd "
                Query := "on sd.f_dsgp = dd.f_dsgp "
                Query := "and sd.f_desc = dd.f_desc "
                Query := "join [IRMNSPC].[dbo].[DESC_GRP] dg "
                Query := "on sd.F_DSGP = dg.F_DSGP "
                Query := "where se.F_FLAG = 0 "
                Query := "and jd.f_name = '":IQSToolID:"' "
                Query := "and pl.f_name = '":GaNRunID:"' "
                Query := "and dg.f_name in ('":ColName:"', 'Wafer Scribe') "
                Query := ") as tableA "
                Query := "group by sd_sgrp, sd_tsno, jd_name, pl_name, pd_name "
                Query := ") as tableB "
                Query := "where [Wafer Scribe] != '<blank>' and [":ColName:"] != '00'"
                Query := "order by sd_sgrp desc, sd_tsno"
                
                Response  = Sql_Services('PostSQLRequest', 'IQSDMS1', Query)
                If Response NE '' then
                    Convert @FM to @VM in Response
                    Convert @RM to @FM in Response
                    WaferData    = SRP_Array('Rotate', Response, @FM, @VM)
                    // Verify wafer scribes with what is stored in OpenInsight.
                    // If a discrepancy is found, set a flag to prevent users from closing the RDS.
                    ReactRunRec  = Database_Services('ReadDataRow', 'REACT_RUN', RDSNo)
                    ScribeErrors = ''
                    RRScribes    = Xlate('REACT_RUN', RDSNo, 'WFR_SCRIBES', 'X')
                    ScribeErrors = Xlate('REACT_RUN', RDSNo, 'SCRIBE_ERROR', 'X')
                    WaferIDs     = WaferData<1>
                    WaferScribes = WaferData<2>
                    For each WaferID in WaferIDs using @VM setting vPos
                        RRScribe                 = RRScribes<0, WaferID>
                        ThisScribeError          = (RRScribe NE WaferScribes<0, vPos>)
                        PrevScribeError          = ScribeErrors<0, WaferID>
                        ScribeErrors<0, WaferID> = (ThisScribeError OR PrevScribeError)
                    Next WaferID
                    ReactRunRec<REACT_RUN_SCRIBE_ERROR$> = ScribeErrors
                    Database_Services('WriteDataRow', 'REACT_RUN', RDSNo, ReactRunRec, True$, False$, True$)
                end
            end
        end else
            ErrorMsg = 'Error in ':Service:' service. Null GaNRunID, WfrTrackPart, or IQSToolID.'
        end
    end else
        ErrorMsg = 'Error in ':Service:' service. Null RDSNo or tool ID passed into service.'
    end
    
end service


Service ClearScribeErrors(RDSNo)
    
    ErrorMsg = ''
    If RDSNo NE '' then
        ReactRunRec = Database_Services('ReadDataRow', 'REACT_RUN', RDSNo)
        If Error_Services('NoError') then
            NumWfrs = DCount(ReactRunRec<REACT_RUN_IN_WFR_ID$>, @VM) 
            For WfrIndex = 1 to NumWfrs
                ReactRunRec<REACT_RUN_SCRIBE_ERROR$, WfrIndex> = False$
            Next WfrIndex
            Database_Services('WriteDataRow', 'REACT_RUN', RDSNo, ReactRunRec, True$, False$, True$)
            If Error_Services('HasError') then
                ErrorMsg = Error_Services('GetMessage')
            end
        end else
            ErrorMsg = Error_Services('GetMessage')
        end
    end else
        ErrorMsg = 'Error in ':Service:' service. Null RDSNo passed in.'
    end
    If ErrorMsg NE '' then
        Error_Services('Add', ErrorMsg)
        Response = False$
    end else
        Response = True$
    end
    
end service


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Internal GoSubs
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

ClearCursors:
    For counter = 0 to 8
        ClearSelect counter
    Next counter
return
