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

    Name        :   QA_Services

    Description :   Handler program for all WO_MAT_QA 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)
        07/24/18    djs     Original programmer. Created the CalculateHgCVData and CalculateThickData services.
        07/30/18    djs     Created the GetLotSODAvg service, which returns the sum of defects average
                            for the entire lot for which a given RDS belongs to.
        08/06/18    djs     Created the GetROTRStatus service, which returns the status of a given WO_STEP record.
        08/08/18    djs     Created the GetMaintenanceScanStatus service, which returns whether or not a 100% (failure)
                            scan is reguired for a given RDS. This is necessary for the first two RDS runs following
                            ROTR related maintenance upon a given reactor. 
        08/14/18    djs     Created the GetUCL service, which returns the Upper Control Limit (SoD Avg) for a given RDS.
                            This specification is managed by and pulled from the SPC Microsoft SQL Database.
        08/30/18    djs     Added the PostWaferImageRequest and ProcessWaferImageRequests services. These services allow
                            wafer map images to be pulled from the Sharepoint database on the EC domain and sent to
                            users on the application server.
        09/11/18    djs     Added the GetSoDPerWafer service. This service queries the Sharepoint database for sum of
                            defect values for each wafer in a given RDS. This is primarily used when any single wafer's
                            SOD value exceeds the USL threshold. When this occurs a 100% failure scan is required where
                            the individual wafer SOD values are required for verfication.
        09/13/18    djs     Added the PostUCLRequest and ProcessUCLRequests services. These services allow UCL values to
                            be retrieved from the SPC database on the Infineon domain and sent to users/services on the
                            EC server.
        09/14/18    djs     Added the GetNumWafersExceedingUSL service, which returns the number of wafers in an RDS
                            that have a Sum of Defect value that exceeds the USL threshold.
        09/18/18    djs     Added the PostToWaferImageQueue and ProcessWaferImageQueue services. These allow wafer pdfs
                            to be retrieved when metrology run data is imported and also allows the pdfs to be copied to
                            the App server via OpenInsight.
        10/08/18    djs     Added the SignPreEpiStage service. This service encapsulates the business logic within the
                            click script event of the sign button on the RDS_PRE_EPI form, so that it can be called
                            from outside the form.
        10/09/18    djs     Added the SignLoadStage and SignUnloadStage services. These services encapsulate the
                            business logic within the click event of the sign button on RDS form and the RDS_UNLOAD
                            form respectively, so that they can be called from outside the forms.
        10/10/18    djs     Added the SignFQAStage service. This service encapsulates the business logic within the
                            click event of the sign button on the RDS_POST_EPI form (located in RDS_POST_EPI_EVENTS),
                            so that it can be called from outside the form.
        10/29/18    djs     Updated PostUCLRequest and ProcessUCLRequest to allow for a particular Tencor recipe to be
                            supplied. (CLEAN_INSP records can have multiple tests associated with them with different
                            recipe names.)
        11/20/18    djs     Refactored ROTR codebase to better align with functional requirements. GetNumUSLFails,
                            GetROTRStatus, GetNumWafersExceedingUSL have all been removed as they are no longer needed.
                            Updated wafer image related services to require a tencor recipe to be specified. This allows
                            the application to store and view wafer images for separate tencor recipes
                            (e.g. 8IN_THIN ROTR vs. 8IN100_ROTR).
        06/13/24	djm		Add new stage-specific supplement system.

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

$insert SERVICE_SETUP
$insert RDS_EQUATES
$insert CLEAN_INSP_EQUATES
$insert REACTOR_EQUATES
$insert REACT_UTIL_EQU
$insert WO_MAT_EQUATES
$insert WO_LOG_EQUATES
$insert RLIST_EQUATES
$insert REACT_MODE_EQUATES
$insert WAFER_IMAGE_REQUESTS_EQUATES
$insert UCL_REQUESTS_EQUATES
$insert WAFER_IMAGE_QUEUE_EQUATES
$insert LSL_USERS_EQUATES
$insert PROD_SPEC_EQUATES
$insert TOOL_EQUATES
$insert PRS_STAGE_EQUATES
$insert SUPPL_INFO_ARRAY_EQU
$insert REACT_STATE_EQUATES
$insert REACT_RUN_EQUATES
$insert REACT_ITEM_EQUATES
$insert APP_INSERTS
$insert SURFACE_SCAN_EQUATES
$insert COMPANY_EQUATES
$insert WO_MAT_QA_EQUATES
$insert SCHED_DET_EQUATES
$insert MSG_EQUATES
$insert EXCEL_EQU
$insert ROTR_REQUESTS_EQUATES
$insert ROTR_EQUATES
$insert REACT_LL_EQUATES
$Insert WAFER_COUNTER_EQUATES
$Insert IFX_EQUATES

* Clean Insp Actions
EQU ACTION$ACTIONS				TO 1
EQU ACTION$SIGS					TO 2

Equ COL$LOG_FILE        to 1
Equ COL$LOG_DTM         to 2
Equ COL$ACTION          to 3
Equ COL$WH_CD           to 4
Equ COL$LOC_CD          to 5
Equ COL$WO_NOS          to 6
Equ COL$CASS_NOS        to 7
Equ COL$USER_ID         to 8
Equ COL$TAGS            to 9
Equ COL$TOOL_ID         to 10

Equ LF$                     to \0A\
Equ Comma$                  to ','

AutoDisplayErrors   = False$     ; // Set this to True$ when debugging so all errors will automatically display.

Declare subroutine  RTI_Set_Debugger, Database_Services, Btree.Extract, Extract_SI_Keys, Rlist, Error_Services
Declare subroutine  Set_Status, Sleepery, Winyield, Yield, SRP_COM, QA_Services, Logging_Services, Obj_RDS
Declare subroutine  Validate, obj_WO_Mat, obj_WO_Mat_Log, obj_React_Status, Record_Lock, obj_React_State, obj_Post_Log
Declare subroutine  RDS_Services, obj_WO_React, RDS_React_Run, Signature_Services, SQL_Services, SRP_Stopwatch
Declare subroutine  Override_Services, Reactor_Services, Lot_Services, Mona_Services, Service_Services, Obj_Notes
Declare subroutine  Transaction_Services
Declare function    SRP_Sort_Array, Metrology_Services, obj_RDS_Test, obj_Test_Point_Map, Database_Services
Declare function    Work_Order_Services, SRP_JSON, Logging_Services, Environment_Services, SRP_Trim, Error_Services
Declare function    Min, Max, SRPSendMail, Btree.Extract, GetTickCount, HTTPClient_Services, Obj_RDS, SQL_Services
Declare function    SRP_Encode, SRP_Decode, SRP_COM, QA_Services, RDS_Services, Obj_NCR, Logging_Services, Obj_Reactor  
Declare function    Get_Status, Set_Status, obj_WO_Mat, NextKey, MemberOf, FieldCount, obj_React_Status, Obj_Clean_Insp
Declare function    Schedule_Services, Signature_Services, Date, Time, Datetime, Date_Services, SRP_Array, Math_Services
Declare function    Supplement_Services, Reactor_Services, Lot_Services, Wafer_Counter_Services

LogDate          = Oconv(Date(), 'D4/')
LogTime          = Oconv(Time(), 'MTS')
LoggingDTM       = LogDate : ' ' : LogTime
LogPath          = Environment_Services('GetApplicationRootPath') : '\LogFiles\ROTR'
QueueLogFileName = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' ROTR Performance Log.csv'
Headers          = 'Logging DTM' : @FM : 'RDS Key ID' : @FM : 'Notes'
QueueObjLog      = Logging_Services('NewLog', LogPath, QueueLogFileName, CRLF$, Comma$, Headers, '', False$, False$)

ErrorLogFileName = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' ROTR Error Log.csv'
objErrorLog      = Logging_Services('NewLog', LogPath, ErrorLogFileName, CRLF$, Comma$, Headers, '', False$, False$)

LogPath             = Environment_Services('GetApplicationRootPath') : '\LogFiles\WaferMap'
WaferMapLogFilename = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' Wafer Map Error Log.csv'
objWaferMapLog      = Logging_Services('NewLog', LogPath, WaferMapLogFileName, CRLF$, Comma$, Headers, '', False$, False$)

LogPath       = Environment_Services('GetApplicationRootPath') : '\LogFiles\FS_Error'
LogFilename   = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' FS Error Log.csv'
Headers       = 'Logging DTM' : @FM : 'User' : @FM : 'RDSNo' : @FM : 'Error Message'
objFSErrorLog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, Comma$, Headers, '', False$, False$)

LogPath           = Environment_Services('GetApplicationRootPath') : '\LogFiles\ReactStatus'
LogFilename       = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' React Status.csv'
Headers           = 'Logging DTM' : @FM : 'User' : @FM : 'CassNo' : @FM : 'RDSNo' : @FM : 'Load DTM' : @FM : 'Service' : @FM : 'Notes'
objReactStatusLog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, Comma$, Headers, '', False$, False$)

IsProd = Environment_Services('IsProd')
If IsProd EQ True$ then
	MonaResource = 'GRP_OPENINSIGHT_MES_OP_FE_QASERVICES'
end else
	MonaResource = 'GRP_OPENINSIGHT_MES_OP_FE_DEV_QASERVICES'
end

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$


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


//----------------------------------------------------------------------------------------------------------------------
// CalculateHgCVData
//
// Datapoints - [Required]
// Returns the average, min, max, edge mean delta, and range percent delimited by @VM
//----------------------------------------------------------------------------------------------------------------------
Service CalculateHgCVData(Datapoints)

    // Find the Min, Max, Average, Edge Mean Delta, and Range % values
    Sum             = 0
    Min             = DataPoints<0,1>
    Max             = DataPoints<0,1>
    Delta1          = 0
    Delta2          = 0
    RangeMin        = DataPoints<0,1>
    RangeMax        = DataPoints<0,1>
    RangeAvg        = 0
    RangePoints     = '1,2,5,6,9'
    RangeDataPoints = ''
    CriticalPoints  = RangePoints
    NumDataPoints   = 0
    Edge4mmPoints   = '3,4,7,8'
    Edge10mmPoints  = '2,5,6,9'
    Edge4mmSum      = 0
    Edge10mmSum     = 0
    
    For each DataPoint in DataPoints using @VM setting Index
        Locate Index in CriticalPoints using ',' setting unusedIndex then
            NumDataPoints += 1
            Sum    = Sum + DataPoint
            // Min & Max
            Min    = Min(Min, DataPoint)
            Max    = Max(Max, DataPoint)
            // Edge Mean Delta
        end        
        // Range %
        Locate Index in RangePoints using ',' setting unusedIndex then
            RangeMin               = Min(RangeMin, DataPoint)
            RangeMax               = Max(RangeMax, DataPoint)
            RangeAvg               = RangeAvg + DataPoint
            RangeDataPoints<0, -1> = DataPoint
        end
        Locate Index in Edge4mmPoints using ',' setting unusedIndex then
            Edge4mmSum += DataPoint
        end
        Locate Index in Edge10mmPoints using ',' setting unusedIndex then
            Edge10mmSum += DataPoint
        end
    Next DataPoint
    // Edge Mean Delta
    RangePct      = ''
    EdgeMeanDelta = ''
    EdgeMean4mm   = Edge4mmSum / 4
    EdgeMean10mm  = Edge10mmSum / 4
    If EdgeMean10mm GT 0 then
        EdgeMeanDelta = ( (Edge4mmSum - Edge10mmSum)  / Edge10mmSum) * 100 ; // Changed divisor to Delta2Avg to match SPC - DJS
        // Range %
        Range           = RangeMax - RangeMin
        NumRangePoints  = DCount(RangePoints, ',')
        RangeAvg        = RangeAvg / NumRangePoints
        RangePct        = (Range / RangeAvg) * 100
    end

    Average         = Sum / NumDataPoints   
    FullAvg         = ''
    TotalDataPoints = DCount(DataPoints, @VM)
    If TotalDataPoints GT 0 then FullAvg = Sum(DataPoints)/DCount(DataPoints, @VM)
    
    ResStdDev = Math_Services('GetStdDev', RangeDataPoints, 'SAMPLE')
    
    Response       = ''
    Response<0, 1> = Average
    Response<0, 2> = Min
    Response<0, 3> = Max
    Response<0, 4> = EdgeMeanDelta
    Response<0, 5> = RangePct
    Response<0, 6> = EdgeMean4mm    ; // 4mm Edge Mean
    Response<0, 7> = FullAvg        ; // 9 point avg
    Response<0, 8> = EdgeMean10mm   ; // 10mm Edge Mean
    Response<0, 9> = ResStdDev

End Service


//----------------------------------------------------------------------------------------------------------------------
// CalculateThickData
//
// Datapoints - [Required]
// Returns the average, minimum, and maximum value delimited by @VM
//----------------------------------------------------------------------------------------------------------------------
Service CalculateThickData(Datapoints)
    // Find the Min, Max, Average, Edge Mean Delta, and Range % values
    Sum          = 0
    Min          = DataPoints<0,1>
    Max          = DataPoints<0,1>
    NumSigPoints = 0
    For each DataPoint in DataPoints using @VM setting Index
    // Data points 11, 12, 13, and 14 are not significant for calculation purposes per management
    Until Index GT 10
        TrimData = Trim(DataPoint)
        If ( (TrimData NE '') and (TrimData NE 0) and (TrimData NE '0.0') ) then
            NumSigPoints += 1
            Sum    = Sum + DataPoint
            // Min & Max
            Min    = Min(Min, DataPoint)
            Max    = Max(Max, DataPoint)
        end
    Next DataPoint
    // Average
    NumDataPoints   = DCount(DataPoints, @VM)
    IF NumSigPoints GT 0 then
        Average         = Sum / NumSigPoints
    end else
        Average = 0
    end
    Average         = OConv(IConv(Average, 'MD3L'), 'MD3L')
    Min             = OConv(IConv(Min, 'MD3L'), 'MD3L')
    Max             = OConv(IConv(Max, 'MD3L'), 'MD3L')
    Response = Average : @VM : Min : @VM : Max

End Service


//----------------------------------------------------------------------------------------------------------------------
// PostROTRRequest
//
// Input:
//      RDSNo  - [Required]
//
//----------------------------------------------------------------------------------------------------------------------
Service PostROTRRequest(RDSNo)
    
    ErrorMessage = ''
    LogData      = ''
    LogData<1>   = LoggingDTM
    LogData<2>   = RDSNo
    LogData<3>   = 'Attempting to post ROTR request for ':RDSNo:'.'
    Logging_Services('AppendLog', objErrorLog, LogData, @RM, @FM)
    If RDSNo NE '' then
        // Look for a pre-existing request in the queue
        hDict = Database_Services('GetTableHandle', 'DICT.ROTR_REQUESTS')
        If Error_Services('NoError') then
            RequestKeyID = ''   
            Query        = 'RDS_NO' : @VM : RDSNo : @FM
            Option       = ''
            Flag         = ''
            Btree.Extract(Query, 'ROTR_REQUESTS', hDict, RequestKeyID, Option, Flag)
            If Flag EQ 0 then
                If RequestKeyID EQ '' then
                    WOMatKey     = Xlate('RDS', RDSNo, 'WO_MAT_KEY', 'X')
					RequestDate  = Date()
					RequestTime  = Time()
					RequestKeyID = RDSNo:'*':RequestDate :'*':RequestTime
					RequestRow   = ''
					Database_Services('WriteDataRow', 'ROTR_REQUESTS', RequestKeyID, RequestRow, True$, False$, True$)
					If Error_Services('NoError') then
						// Log success
						LogData<3>   = 'Successfully posted ROTR request for ':RDSNo:'.'
						Logging_Services('AppendLog', objErrorLog, LogData, @RM, @FM)
					end else
						ErrorMessage = Error_Services('GetMessage')                       
					end
                end else
                    ErrorMessage = 'Duplicate request found for RDS ':RDSNo:'. Request ignored.'
                end
            end else
                BtreeError   = Status()
                ErrorMessage = 'Btree.Extract call failed. Error code: ':BtreeError:'.'
            end
        end else
            ErrorMessage = Error_Services('GetMessage')
        end
    end else
        ErrorMessage = 'Null parameter passed into service call. All parameters are required.'
    end
    If ErrorMessage NE '' then
        LogData<3>   = 'Error posting ROTR request for ':RDSNo:'. Error message: ':ErrorMessage
        Logging_Services('AppendLog', objErrorLog, LogData, @RM, @FM)
        Error_Services('Add', ErrorMessage)
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// ProcessROTRRequests
//
//----------------------------------------------------------------------------------------------------------------------
Service ProcessROTRRequests()

    hSysLists   = Database_Services('GetTableHandle', 'SYSLISTS')
    Lock hSysLists, ServiceKeyID then
        Tablename     = 'ROTR_REQUESTS'
        hROTRRequests = Database_Services('GetTableHandle', Tablename)
        If Error_Services('NoError') then
            GoSub ClearCursors
            Set_Status(0)
            SortList = "RESPONSE_DATE" : @FM : "RESPONSE_TIME"
            Cursor   = 1
            Select TableName By SortList Setting Cursor then
                EOF         = False$
                Loop
                    ReadNext RequestKeyID Using Cursor else EOF = True$
                Until EOF                      
                    Lock hROTRRequests, RequestKeyID then
                        RDSNo       = Field(RequestKeyID, '*', 1)
                        RequestDate = Field(RequestKeyID, '*', 2)
                        RequestTime = Field(RequestKeyID, '*', 3)
                        If ( (RDSNo NE '') and (RequestDate NE '') and (RequestTime NE '') ) then 
                            RequestRow  = Database_Services('ReadDataRow', 'ROTR_REQUESTS', RequestKeyID)                                                
                            QA_Services('ProcessROTRRequest', RDSNo)
                            If Error_Services('NoError') then
                                // Request was successful, so remove the request from the queue
                                LogDate     = Date()
                                LogTime     = Time()
                                TimeTaken   = LogTime - RequestTime
                                Database_Services('DeleteDataRow', 'ROTR_REQUESTS', RequestKeyID, True$)
                                LogData     = ''
                                LogData<1>  = Oconv(LogDate, 'D4/')  : ' ' : Oconv(LogTime, 'MTS')
                                LogData<2>  = RDSNo
                                LogData<3>  = 'Request successfully processed. Time taken: ':TimeTaken:' seconds. Deleting ROTR request from queue.'
                                Logging_Services('AppendLog', QueueObjLog, LogData, @RM, @FM)
                            end else
                                ErrorMsg   = Error_Services('GetMessage')
                                LogData    = ''
                                LogData<1> = Oconv(Date(), 'D4/')  : ' ' : Oconv(Time(), 'MTS')
                                LogData<2> = RDSNo
                                LogData<3> = 'Request failed to process. Error message: ':ErrorMsg
                                Logging_Services('AppendLog', QueueObjLog, LogData, @RM, @FM)                                              
                                // Keep request in the queue (i.e. do not delete it)
                                RequestRow<ROTR_REQUESTS.RESPONSE_TIME$> = Time()
                                RequestRow<ROTR_REQUESTS.RESPONSE_DATE$> = Date()
                                RequestRow<ROTR_REQUESTS.RESPONSE$>      = ErrorMsg
                                Database_Services('WriteDataRow', 'ROTR_REQUESTS', RequestKeyID, RequestRow, True$, False$, True$)                       
                                Unlock hROTRRequests, RequestKeyID else
                                    Error_Services('Add', 'Error unlocking ':RequestKeyID:', ':Tablename:' within ':Service)
                                end
                            end
                        end else
                            LogData    = ''
                            LogData<1> = Oconv(LogDate, 'D4/')  : ' ' : Oconv(LogTime, 'MTS')
                            LogData<2> = RequestKeyID
                            LogData<3> = 'Malformed request selected from ROTR_REQUESTS table in ':Service:' service.'
                            Logging_Services('AppendLog', QueueObjLog, LogData, @RM, @FM)       
                        end
                    end
                Repeat
            end else
                StatusCode = Status()
                LogData    = ''
                LogData<1> = Oconv(LogDate, 'D4/')  : ' ' : Oconv(LogTime, 'MTS')
                LogData<2> = ''
                LogData<3> = 'Error selecting ROTR_REQUESTS in ':Service:' service. Error code ':StatusCode
                Logging_Services('AppendLog', QueueObjLog, LogData, @RM, @FM)       
            end
        end else
            ErrorMsg = Error_Services('GetMessage')
        end
        Unlock hSysLists, ServiceKeyID else Null    
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// ProcessROTRRequest
//
// Input:
//      RDSNo  - [Required]
//
//----------------------------------------------------------------------------------------------------------------------
Service ProcessROTRRequest(RDSNo)
  
    ErrorMessage = ''
    LogData      = ''
    LogData<1>   = LoggingDTM
    LogData<2>   = RDSNo
    LogData<3>   = 'Attempting to process ROTR request for ':RDSNo:'.'
    Logging_Services('AppendLog', objErrorLog, LogData, @RM, @FM)
    If RDSNo NE '' then
        LWICINo = Xlate('RDS', RDSNo, 'LWI_CI_NO', 'X')
        If LWICINo NE '' then
            HaveLock = Database_Services('GetKeyIDLock', 'CLEAN_INSP', LWICINo)
            If HaveLock EQ True$ then       
                CIRec    = Database_Services('ReadDataRow', 'CLEAN_INSP', LWICINo)
                If Error_Services('NoError') then
                    Response     = QA_Services('GetROTRStatus', RDSNo)               
                    If Error_Services('NoError') then
                        ResponseCopy                                           = Response
                        Swap @FM with ',' in ResponseCopy
                        LogData<3>                                             = 'ROTR Status response: ':Response
                        Logging_Services('AppendLog', objErrorLog, LogData, @RM, @FM)                                
                        CIRec<CLEAN_INSP_ROTR_REACTOR_STATUS$>                 = Response<1>
                        CIRec<CLEAN_INSP_ROTR_REACTOR_STATUS_REASON$>          = Response<2>
                        CIRec<CLEAN_INSP_ROTR_REACTOR_NCR_COUNT$>              = Response<3>
                        CIRec<CLEAN_INSP_ROTR_REACTOR_ZERO_NCR_RUN_COUNT$>     = Response<4>
                        CIRec<CLEAN_INSP_ROTR_REACTOR_UCL_EXCEEDED_RUN_COUNT$> = Response<5>
                        // Set ROTR Update Flag to trigger CLEAN_INSP_ACTIONS routine (via BASE_MFS).
                        // That routine will recalculate the final ROTR status. 
                        Database_Services('WriteDataRow', 'CLEAN_INSP', LWICINo, CIRec, True$, False$, True$)        			
                    end else
                        ErrorMessage = 'Error in service ':Service:'. Failed to retrieve ROTR status. Error message: ':Error_Services('GetMessage')
                        LogData<3>   = ErrorMessage
                        Logging_Services('AppendLog', objErrorLog, LogData, @RM, @FM)
                    end
                end else
                    ErrorMessage = 'Error in service ':Service:'. Failed to read CLEAN_INSP record: ':LWICINo:'. Error message: ':Error_Services('GetMessage')
                    LogData<3>   = ErrorMessage
                    Logging_Services('AppendLog', objErrorLog, LogData, @RM, @FM)
                end
                Database_Services('ReleaseKeyIDLock', 'CLEAN_INSP', LWICINo)
            end else
                ErrorMessage = 'Error in service ':Service:'. Failed to lock CLEAN_INSP record: ':LWICINo:'. Error message: ':Error_Services('GetMessage')
                LogData<3>   = ErrorMessage
                Logging_Services('AppendLog', objErrorLog, LogData, @RM, @FM)
            end
        end else
            ErrorMessage = 'Error in service ':Service:'. Failed to find an LWI CLEAN_INSP key for RDS ':RDSNo:'.'
            LogData<3>   = ErrorMessage
            Logging_Services('AppendLog', objErrorLog, LogData, @RM, @FM)
        end
    end else
        ErrorMessage = 'Error in service ':Service:'. Null RDSNo passed in.'
        LogData<3>   = ErrorMessage
        Logging_Services('AppendLog', objErrorLog, LogData, @RM, @FM)
    end
    If ErrorMessage NE '' then Error_Services('Add', ErrorMessage)

end service


//----------------------------------------------------------------------------------------------------------------------
// GetROTRStatus
//
// Input: RDSKey - [Required]
//
// Response: ROTRStatus
//
// Returns the failure scan status of the work order associated with a given RDS. Note this is specifically designed
// for ASM, HTR, and ASM+. This procedure would need to be updated to support EpiPro and/or GAN.
// 
//----------------------------------------------------------------------------------------------------------------------
Service GetROTRStatus(RDSKey)
	
    RDSRec               = Database_Services('ReadDataRow', 'RDS', RDSKey)
    WONo                 = RDSRec<RDS_WO$>
    RDSCassNo            = RDSRec<RDS_CASS_NO$>
    ReactorKey           = RDSRec<RDS_REACTOR$>
    CurrPSN              = RDSRec<RDS_PROD_SPEC_ID$>
    ReactorRec           = Database_Services('ReadDataRow', 'REACTOR', ReactorKey)
    // These next six variables are set by engineering per reactor.
    XPrevRuns            = ReactorRec<REACTOR_X_PREV_RUNS$>
    NCRLimit             = ReactorRec<REACTOR_NCR_LIMIT$>
    ZeroNCRMin           = ReactorRec<REACTOR_ZERO_NCR_MIN$>
    UCLExceededLimit     = ReactorRec<REACTOR_UCL_LIMIT$>
    StopRDS              = ReactorRec<REACTOR_ROTR_STOP_RDS$>
    ROTRMaintReset       = ReactorRec<REACTOR_ROTR_MAINT_RESET$>
    ROTRStatus           = 'P'
    ROTRFailReason       = ''
    RunCount             = 0
    NCRCount             = 0
    ZeroNCRCount         = 0
    UCLExceededCount     = 0
    NumOverrideSigs      = 0
    NumBlockOverrideSigs = 0
    WafersProcessed      = 0
    PrevCass             = RDSCassNo - 1
    ThisRDSNo            = ''
    Done                 = False$
    // Collect QA data for current work order. PSNs will match within current work order, so no need to check.
    For Cass = PrevCass to 1 Step -1
        WOMatKey       = WONo :'*': Cass
        WOMatRec       = Database_Services('ReadDataRow', 'WO_MAT', WOMatKey)
        ThisRDSNo      = WOMatRec<WO_MAT_RDS_NO$>
        If ROTRMaintReset EQ True$ then If ThisRDSNo EQ StopRDS then Done = True$
    While (RunCount LE XPrevRuns - 1) and (NCRCount LT NCRLimit) and (UCLExceededCount LT UCLExceededLimit) |
      and (Done EQ False$)
        RunCount       = RunCount + 1
        CleanInspKey   = Xlate('RDS', ThisRDSNo, 'LWI_CI_NO', 'X')
        CIRec = Database_Services('ReadDataRow', 'CLEAN_INSP', CleanInspKey)
        If Error_Services('NoError') then
            ScanRecipes    = CIRec<CLEAN_INSP_SCAN_RECIPE$>
            SpecRecipes    = CIRec<CLEAN_INSP_SPEC_SURFSCAN_RECIPE$>
            // Updated code to look at the original scanned SOD average value before we remove the failed
            // wafers within CLEAN_INSP_ACTIONS.
            ScanSODAvgs    = CIRec<CLEAN_INSP_SCAN_SUM_OF_DEF_AVG_FAIL$>
            SpecSODAvgs    = CIRec<CLEAN_INSP_SPEC_SUM_OF_DEF_AVG$>
            OverrideSigs   = CIRec<CLEAN_INSP_SIGN_ROTR_SIGNATURE$>
            ROTRReasons    = CIRec<CLEAN_INSP_ROTR_ACTION_REASON$>
            NumScanRecipes = DCount(ScanRecipes, @VM)
            UCLExceeded    = False$
            // Check each spec recipe to find latest matching scan. Then check if the SOD Avg (UCL) has been exceeded.
            For Each SpecRecipeName in SpecRecipes using @VM setting SpecRecipeIndex                             
                // Look for the last scan entry related to the current spec recipe.
                RecipeFound = False$
                For ScanRecipeIndex = NumScanRecipes To 1 Step -1
                    ScanRecipeName  = ScanRecipes<0, ScanRecipeIndex>
                    RecipeFound     = (ScanRecipeName EQ SpecRecipeName)
                Until RecipeFound
                Next ScanRecipeIndex
                If RecipeFound EQ True$ then                       
                    ScanSODAvg  = OConv(ScanSODAvgs<0, ScanRecipeIndex>, 'MD3')
                    SpecSODAvg  = SpecSODAvgs<0, ScanRecipeIndex>
                    OverrideSig = OverrideSigs<0, ScanRecipeIndex>
                    ROTRReason  = ROTRReasons<0, ScanRecipeIndex>
                    If (SpecSODAvg EQ '') or (SpecSODAvg EQ 0) then
                        SpecSODAvg = QA_Services('PostUCLRequest', ThisRDSNo, SpecRecipeName)
                        If (SpecSODAvg NE '') and (SpecSODAvg NE 0) then
                            SpecSODAvgs<0, SpecRecipeIndex> = SpecSODAvg
                            CIRec<CLEAN_INSP_SPEC_SUM_OF_DEF_AVG$> = SpecSODAvgs
                            Database_Services('WriteDataRow', 'CLEAN_INSP', CleanInspKey, CIRec, True$, False$, True$)
                        end                                                          
                    end
                    If (SpecSODAvg NE '') and (SpecSODAvg NE 0) then
                        // A null or zero value for the spec SOD Avg (UCL) indicates there was a problem
                        // retrieving  the UCL value from SPC. Checking here to avoid runtime errors.
                        UCLExceeded = (ScanSODAvg GT SpecSODAvg)
                    end
                    If UCLExceeded EQ True$ then UCLExceededCount += 1
                    If OverrideSig NE '' then NumOverrideSigs += 1
                    If (ROTRReason EQ 'NCR Limit Exceeded') or (ROTRReason EQ 'Zero NCR Minimum Not Met') |
                    or (ROTRReason EQ 'UCL Run Count Exceeded') then NumBlockOverrideSigs += 1
                end
            Until UCLExceeded EQ True$
            Next SpecRecipeName
            ThisNCRCount = Xlate('RDS', ThisRDSNo, 'TOT_REJ', 'X')
            If ThisNCRCount EQ 0 then
                ZeroNCRCount += 1           
            end else
                NCRCount += ThisNCRCount
            end
        end else
            Error = Error_Services('GetMessage')
        end
    Next Cass
    
    // Check if we have met XPrevRuns Criteria. If not, then look at previous work orders associated with this reactor.
    // Work with most recent runs to oldest runs. Previous work order PSNs must match the current RDS' PSN.
    If (RunCount LE XPrevRuns - 1) and (NCRCount LT NCRLimit) and (UCLExceededCount LT UCLExceededLimit) |
      and (Done EQ False$) then
        CurrWorkOrder = WONo
        StartDate     = Date() - 90
        WorkOrderList = ''
        SchedDetKeys = Schedule_Services('GetScheduleDetailKeys', StartDate, Date(), ReactorKey, '', '')
        For each SchedDetKey in SchedDetKeys
            SchedDet = Schedule_Services('GetScheduleDetail', SchedDetKey)
            ThisWorkOrder = SchedDet<SCHED_DET_WO_NO$>
            Locate ThisWorkOrder in WorkOrderList using @FM setting fPos else
                If ThisWorkOrder NE CurrWorkOrder then
                    WorkOrderList = Insert(WorkOrderList, 0, 0, 0, ThisWorkOrder)
                end
            end
        Next SchedDetKey
        For each WorkOrder in WorkOrderList using @FM setting fPos
            WOMatKeys     = Xlate('WO_LOG', WorkOrder, WO_LOG_WO_MAT_KEY$, 'X')
            NumWOMatKeys  = DCount(WoMatKeys, @VM)
            For WOMatKeyIndex = NumWOMatKeys to 1 Step -1
                ThisWOMatKey     = WOMatKeys<1, WOMatKeyIndex>
                ThisRDSNo        = Xlate('WO_MAT', ThisWOMatKey, 'RDS_NO', 'X')
                If ROTRMaintReset EQ True$ then If ThisRDSNo EQ StopRDS then Done = True$
            While (RunCount LE XPrevRuns - 1) and (NCRCount LT NCRLimit) and (UCLExceededCount LT UCLExceededLimit) |
              and (Done EQ False$)
                RunCount         = RunCount + 1
                CleanInspKey     = Xlate('RDS', ThisRDSNo, 'LWI_CI_NO', 'X')
                CIRec = Database_Services('ReadDataRow', 'CLEAN_INSP', CleanInspKey)
                If Error_Services('NoError') then
                    ScanRecipes    = CIRec<CLEAN_INSP_SCAN_RECIPE$>
                    SpecRecipes    = CIRec<CLEAN_INSP_SPEC_SURFSCAN_RECIPE$>
                    // Updated code to look at the original scanned SOD average value before we remove the failed
                    // wafers within CLEAN_INSP_ACTIONS.
                    ScanSODAvgs    = CIRec<CLEAN_INSP_SCAN_SUM_OF_DEF_AVG_FAIL$>
                    SpecSODAvgs    = CIRec<CLEAN_INSP_SPEC_SUM_OF_DEF_AVG$>
                    OverrideSigs   = CIRec<CLEAN_INSP_SIGN_ROTR_SIGNATURE$>
                    ROTRReasons    = CIRec<CLEAN_INSP_ROTR_ACTION_REASON$>
                    NumScanRecipes = DCount(ScanRecipes, @VM)
                    UCLExceeded    = False$
                    // Check each spec recipe to find latest matching scan.
                    // Then check if the SOD Avg (UCL) has been exceeded.
                    For Each SpecRecipeName in SpecRecipes using @VM setting SpecRecipeIndex                             
                        // Look for the last scan entry related to the current spec recipe.
                        RecipeFound = False$
                        For ScanRecipeIndex = NumScanRecipes To 1 Step -1
                            ScanRecipeName  = ScanRecipes<0, ScanRecipeIndex>
                            RecipeFound     = (ScanRecipeName EQ SpecRecipeName)
                        Until RecipeFound
                        Next ScanRecipeIndex
                        If RecipeFound EQ True$ then                       
                            ScanSODAvg  = OConv(ScanSODAvgs<0, ScanRecipeIndex>, 'MD3')
                            SpecSODAvg  = SpecSODAvgs<0, ScanRecipeIndex>
                            OverrideSig = OverrideSigs<0, ScanRecipeIndex>
                            ROTRReason  = ROTRReasons<0, ScanRecipeIndex>
                            If (SpecSODAvg EQ '') or (SpecSODAvg EQ 0) then
                                SpecSODAvg = QA_Services('PostUCLRequest', ThisRDSNo, SpecRecipeName)
                                If (SpecSODAvg NE '') and (SpecSODAvg NE 0) then
                                    SpecSODAvgs<0, SpecRecipeIndex> = SpecSODAvg
                                    CIRec<CLEAN_INSP_SPEC_SUM_OF_DEF_AVG$> = SpecSODAvgs
                                    Database_Services('WriteDataRow','CLEAN_INSP',CleanInspKey,CIRec,True$ False$,True$)
                                end                                                          
                            end                                     
                            If (SpecSODAvg NE '') and (SpecSODAvg NE 0) then
                                // A null or zero value for the spec SOD Avg (UCL) indicates there was a problem
                                // retrieving the UCL value from SPC. Checking here to avoid any runtime errors.
                                UCLExceeded = (ScanSODAvg GT SpecSODAvg)
                            end
                            If UCLExceeded EQ True$ then UCLExceededCount += 1
                            If OverrideSig NE '' then NumOverrideSigs += 1
                            If (ROTRReason EQ 'NCR Limit Exceeded') or (ROTRReason EQ 'Zero NCR Minimum Not Met') |
                            or (ROTRReason EQ 'UCL Run Count Exceeded') then NumBlockOverrideSigs += 1
                        end
                    Until UCLExceeded EQ True$
                    Next SpecRecipeName
                    ThisNCRCount = Xlate('RDS', ThisRDSNo, 'TOT_REJ', 'X')
                    If ThisNCRCount EQ 0 then
                        ZeroNCRCount += 1           
                    end else
                        NCRCount += ThisNCRCount
                    end
                end else
                    Error = Error_Services('GetMessage')
                end
            Next WOMatKeyIndex
        Next WorkOrder
      end
      
    // Check QA Criteria
    Begin Case
        Case NCRCount GE NCRLimit
            ROTRStatus = 'F'
            ROTRFailReason = 'NCR Limit Exceeded'
        Case (ZeroNCRCount LT ZeroNCRMin) and (RunCount GE ZeroNCRMin)
            ROTRStatus = 'F'
            ROTRFailReason = 'Zero NCR Minimum Not Met'
        Case UCLExceededCount GE UCLExceededLimit
            ROTRStatus = 'F'
            ROTRFailReason = 'UCL Run Count Exceeded'
        Case Otherwise$
			// All Pass - Allow auto-sign ROTR and reset ROTR override counter logic
			ReactorRec<REACTOR_ROTR_OVERRIDE_COUNT$> = 0
			ReactorRec<REACTOR_PREVIOUS_ROTR_STATUS_REASON$> = ''
			ReactorRec<REACTOR_PREVIOUS_ROTR_OVERRIDE_RDS$> = ''
			Database_Services('WriteDataRow', 'REACTOR', ReactorKey, ReactorRec, True$, False$, True$)
    End Case
    
    ReactorYield = ''
    If RunCount NE 0 then
        WafersProcessed = RunCount * 25
        ReactorYield = (WafersProcessed - NCRCount) / WafersProcessed
    end
    
    Response    = ''
    Response<1> = ROTRStatus
    Response<2> = ROTRFailReason
    Response<3> = NCRCount
    Response<4> = ZeroNCRCount
    Response<5> = UCLExceededCount
    Response<6> = RunCount
    Response<7> = NumOverrideSigs
    Response<8> = NumBlockOverrideSigs
    Response<9> = ReactorYield
   
end service


//----------------------------------------------------------------------------------------------------------------------
// GetMaintenanceScanStatus
//
// Input: RDSKey - [Required]
//
// Response: True$(1) or False$(0)
//
// The initial two RDS runs following reactor maintenance related to ROTR issues must undergo a 100% Tencor scan.
// This service checks to see if an RDS falls into this requirement. If so, then true$ is returned, else false$.
//----------------------------------------------------------------------------------------------------------------------
Service GetMaintenanceScanStatus(RDSKey)

    Response  = False$
    RDSRec    = Database_Services('ReadDataRow', 'RDS', RDSKey)
    ReactorNo = RDSRec<RDS_REACTOR$>
    CurrMode  = Xlate('REACTOR', ReactorNo, 'CURR_MODE', 'X')
    If CurrMode EQ 'P' then
        // Reactor is in production mode. Check if it recently underwent ROTR maintenance.
        SearchStart = Date() - 30
        Cursor      = ''        
        EOF         = False$
        Query = 'SELECT REACTOR_LOG WITH REACTOR EQ ':ReactorNo:' AND WITH START_DATE >= ':Quote(OConv(SearchStart, 'D4/')) |
              : ' BY-DSND START_DATE BY-DSND START_TIME'
        GoSub ClearCursors
        Rlist(Query, Target_ActiveList$, '', '', '')
        // Inspect most recent reactor log to see if it was ROTR related.
        If @RecCount then
            ReadNext ReactorLogID else ReactorLogID = ''   
            If ReactorLogID NE '' then
                ROTRMaint      = Xlate('REACTOR_LOG', ReactorLogID, 'ROTR', 'X')
                If ROTRMaint EQ True$ then
                    // Most recent maintenance was ROTR related, therefore the first two RDS runs require
                    // 100% failure scans to ensure reactor is operating properly.
                    ProdStartDTM  = Xlate('REACTOR', ReactorNo, 'CURR_MODE_DTM', 'X')
                    WOStepKey     = RDSRec<RDS_WO_STEP_KEY$>
                    ProdStartDate = ProdStartDTM[1, 'F.']
                    ProdStartTime = ProdStartDTM[-1, 'B.']
                    WOStepRDSKeys = ''
                    FailFlag      = ''

                    // Count number of RDS runs since reactor entered production mode.  
                    Query  = 'SELECT RDS WITH DATE_IN > ' : Quote(OConv(ProdStartDate, 'D4/'))
                    Query := ' OR WITH DATE_IN = ' : Quote(OConv(ProdStartDate, 'D4/'))
                    Query := ' AND WITH REACTOR = ' : ReactorNo
                    GoSub ClearCursors
                    Set_Status(0)
                    Rlist(Query, Target_ActiveList$, '', '', '')
                    ErrorCode = ''
                    If Get_Status(ErrorCode) then
                        ErrorMsg = 'Error calling rlist in the ' : Service : ' service. Error code: ':ErrorCode
                        Error_Services('Add', ErrorMsg)
                    end
                    RunsSinceProdDate = @RecCount
                    // Now we need to subtract the runs on ProdStartDate, but before ProdStartTime.
                    // Truncate production start time to speed up query.                   
                    ProdStartTime = ProdStartTime[1,5]
                    Query  = 'SELECT RDS WITH TIME_IN < ' : ProdStartTime
                    Query := ' AND WITH DATE_IN = ' : Quote(OConv(ProdStartDate, 'D4/'))
                    Query := ' AND WITH REACTOR = ' : ReactorNo
                    GoSub ClearCursors
                    Set_Status(0)
                    Rlist(Query, Target_ActiveList$, '', '', '')
                    If Get_Status(ErrorCode) then
                        ErrorMsg = 'Error calling rlist in the ' : Service : ' service. Error code: ':ErrorCode
                        Error_Services('Add', ErrorMsg)
                    end
                    RunsBeforeProdTime = @RecCount
                    RunsSinceProdDTM = RunsSinceProdDate - RunsBeforeProdTime
                    // If the number of runs since production mode began is less than 2,
                    // then 100% failure scan is required for this RDS. -> Return True$
                    If RunsSinceProdDTM LE 2 then
                        Response = True$
                    end
                    GoSub ClearCursors
                end
            end
        end
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// GetUCL
//
// Input: RDSKey       - [Required]
//        TencorRecipe - [Optional] - If no recipe is supplied, then this service will attempt to look for the
//                                    most recent scan to use to fetch the associated UCL value.
//
// Response: Upper Control Limit (UCL) Specification from SPC database for the given RDS.
//
// Note: This can't be called directly from the EC server because the SPC database is not within the EC domain.
//       Use the PostUCLRequest service within this service module to retrieve the UCL value. This request will
//       be processed on the App server (if the Process UCL Requests service is running in the service manager)
//----------------------------------------------------------------------------------------------------------------------
Service GetUCL(RDSKey, TencorRecipe)

    If RDSKey NE '' then
        RDSRec     = Database_Services('ReadDataRow', 'RDS', RDSKey)
        If Error_Services('NoError') then
            ReactorKey = RDSRec<RDS_REACTOR$>
            If ReactorKey EQ '' then
                ReactorKey = Xlate('RDS', RDSKey, 'SCHED_REACTOR', 'X')
            end
            LWICIKey          = Xlate('RDS', RDSKey, 'LWI_CI_NO', 'X')
            If TencorRecipe EQ '' then
                // No recipe name supplied, so grab the most recent scan recipe name.
                ScanRecipes  = Xlate('CLEAN_INSP', LWICIKey, CLEAN_INSP_SPEC_SURFSCAN_RECIPE$, 'X')
                TencorRecipe = ScanRecipes<1,1>
            end
            TestName          = 'Average Sum of Defects'
            ReactorUCLMean    = ''
            ReactorUCLSigma   = ''
            ReactorUCLData    = ''
            UCL               = ''
            
            QueryStatement  = 'SELECT [F_MEAN] as ProcessMean,[F_SP] as ProcessSigma ' |
                            : 'FROM [SPCEPIWORLD].[dbo].[CTRL_LIM] CTRL ' |
                            : 'JOIN [SPCEPIWORLD].[dbo].[PART_DAT] PART ON PART.F_PART = CTRL.F_PART ' |
                            : 'JOIN [SPCEPIWORLD].[dbo].[PRCS_DAT] PROCESS ON PROCESS.F_PRCS = CTRL.F_PRCS ' |
                            : 'JOIN [SPCEPIWORLD].[dbo].[TEST_DAT] TEST ON TEST.F_TEST = CTRL.F_TEST ' |
                            : "WHERE PART.F_NAME = '" : TencorRecipe : "' AND " |
                            : "PROCESS.F_NAME = '" : ReactorKey : "' AND " |
                            : "TEST.F_NAME = '" : TestName : "'"
            ReactorUCLData  = SQL_Services('GetDataRows', 'SPC', QueryStatement)
            // Occassionally more than one record will be returned. Just grab the first record.
            ReactorUCLData = Field(ReactorUCLData, @RM, 1)
            If ReactorUCLData NE '' then
                ReactorUCLMean  = ReactorUCLData<1>
                ReactorUCLSigma = ReactorUCLData<2>
                UCL             = ReactorUCLMean + ( 3 * ReactorUCLSigma )
            end        
        end else
            ErrorMessage = Error_Services('GetMessage')
        end
        Response = UCL   
    end
     
end service


//----------------------------------------------------------------------------------------------------------------------
// PostUCLRequest
//
// Input:
//  RDSNo        - RDS number associated with the desired UCL. [Required]
//  TencorRecipe - Particular Tencor recipe associated with the desired UCL. [Optional]
//
// Output:
//  Response  - Upper Control Limit (UCL) associated with the given RDS. 
//
// Posts a wafer map image request to the UCL_REQUESTS database table, which is then 
// processed on the APP server by the Process UCL Requests service.
//----------------------------------------------------------------------------------------------------------------------
Service PostUCLRequest(RDSNo, TencorRecipe)

    UCL          = ''
    RequestDate  = Date()
    RequestTime  = Time()
    RequestKeyID = RequestDate : '*' : RequestTime : '*' : RDSNo
    RequestRow   = ''
    If TencorRecipe NE '' then RequestRow<UCL_REQUESTS.TENCOR_RECIPE$> = TencorRecipe
    Database_Services('WriteDataRow', 'UCL_REQUESTS', RequestKeyID, RequestRow, True$, '', True$)
    If Error_Services('NoError') then
        TimeExpired = False$
        Start       = GetTickCount()
        Loop
            RequestRow      = Database_Services('ReadDataRow', 'UCL_REQUESTS', RequestKeyID)
            ResponseDate    = RequestRow<UCL_REQUESTS.RESPONSE_DATE$>
            If ResponseDate NE '' then
                UCL = RequestRow<UCL_REQUESTS.UCL$>
                If UCL EQ '' then Error_Services('Add', 'UCL not found for RDS: ' : RDSNo : ' in service :' Service)
            end
            // Time will expire after 30 seconds.
            If GetTickCount() - Start GE 30000 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', 'Timed out attempting to retrieve UCL for RDS: ': RDSNo)
        end
        Database_Services('DeleteDataRow', 'UCL_REQUESTS', RequestKeyID, True$)
    end else
        ErrorMsg = Error_Services('GetMessage')
        Error_Services('Add', 'Error posting UCL request for RDS: ': RDSNo : ' Error: ' : ErrorMsg)
    end
    
    Response    = UCL

return


//----------------------------------------------------------------------------------------------------------------------
// ProcessUCLRequests
//
// Service that attempts to process all UCL requests. These requests are queued in the
// UCL_REQUESTS table and are to be processed on the App server (at least while SPC database is not in EC domain).
//----------------------------------------------------------------------------------------------------------------------
Service ProcessUCLRequests()
    
    hValidationRequests = Database_Services('GetTableHandle', 'UCL_REQUESTS')
    If Error_Services('NoError') then
        Sentence    = 'SELECT UCL_REQUESTS 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', 'UCL_REQUESTS', RequestKeyID)
                RDSNo        = Field(RequestKeyID, '*', 3, 1)
                TencorRecipe = RequestRow<UCL_REQUESTS.TENCOR_RECIPE$>
                If TencorRecipe NE '' then
                    RequestRow<UCL_REQUESTS.UCL$> = QA_Services('GetUCL', RDSNo, TencorRecipe)
                end else
                    RequestRow<UCL_REQUESTS.UCL$> = QA_Services('GetUCL', RDSNo)
                end
                RequestRow<UCL_REQUESTS.RESPONSE_DATE$> = Date()
                RequestRow<UCL_REQUESTS.RESPONSE_TIME$> = Time()
                Database_Services('WriteDataRow', 'UCL_REQUESTS', RequestKeyID, RequestRow, True$)
                Unlock hValidationRequests, RequestKeyID else Null
            end
        Repeat
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// GetSODPerWafer
//
// Input: RDSKey - [Required]
//
// Response: The Sum of Defects (SOD) for each wafer in a given cassette (RDS). This is used when the SOD of any given
//           exceeds the Upper Control Limit (UCL).
//----------------------------------------------------------------------------------------------------------------------
Service GetSODPerWafer(RDSKey, TencorRecipe, ScanDTM)

    If RDSKey NE '' and TencorRecipe NE '' then
              
        If Num(ScanDTM) then ScanDTM = OConv(ScanDTM, 'DT/^S')
        Query  = "DECLARE @RDS         varchar(10) "                                            
        Query := "DECLARE @RECIPE      varchar(30) "
        Query := "DECLARE @INSERT_DT   datetime "
        Query := "SET @RDS      = '":RDSKey:"' "                                                
        Query := "SET @RECIPE   = '":TencorRecipe:"' "
        Query := "SET @INSERT_DT = '":ScanDTM:"' "
        Query := "SELECT child.slot, child.sumofdefects, child.sort "                           
        Query := "FROM Metrology.dbo.TencorRunHeader header "                                   
        Query := "INNER JOIN Metrology.dbo.TencorRunData child on header.id = child.headerid "  
        Query := "WHERE header.rds = @RDS AND header.recipe like @RECIPE + '%' "
        If ScanDTM NE '' then
            Query := "AND Child.InsertDate BETWEEN (SELECT(DATEADD(minute, -5, @INSERT_DT))) and (SELECT(DATEADD(minute, 5, @INSERT_DT))) "
        end
        Query := "ORDER BY header.insertDate DESC, child.slot ASC"
                          
        MetrologyWaferRunData  = SQL_Services('PostSQLRequest', 'SPC', Query)
        Response        = ''        
        // Data validation
        // Ensure WaferNo column is formatted correctly (* followed by two integers)
        ValidData = True$
        Done      = False$
        NumWafers = DCount(MetrologyWaferRunData, @RM)
        
        For WaferIndex = NumWafers to 1 Step -1
            Wafer = Field(MetrologyWaferRunData, @RM, WaferIndex)
            Until ValidData EQ False$
            WaferNo = Wafer<1>
            If WaferNo NE '' then
            	Convert '*' to '' in WaferNo
                ValidData = ( (WaferNo Matches '1N') or (WaferNo Matches '2N') )
                If ValidData then                
                    If WaferNo[1, 1] EQ 0 then WaferNo [1, 1] = ''
                    If WaferNo EQ 1 then Done = True$
                    SumOfDefects = Wafer<2>
                    Sort         = Wafer<3>
                    Locate WaferNo in Response<1> using @VM setting WaferPos else
                        // No SOD value for this wafer yet recorded, so add it to the response array.
                        Response<1, WaferNo> = WaferNo
                        Response<2, WaferNo> = SumOfDefects
                        Response<3, WaferNo> = Sort
                    end
                end
            end
        Until Done EQ True$
        Next WaferIndex
        
        If ValidData EQ False$ then
            ErrorMsg = 'Malformed data received'
        end

    end else
        ErrorMsg = 'Null value for RDSKey or TencorRecipe passed in. Both values are required.'
        Error_Services('Add', ErrorMsg)
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// PostWaferImageRequest
//
// Input:
//  Username  - Username of user whom is posting the request.
//  RDSNo     - RDS number associated with the desired wafer image.
//  WaferNo   - Wafer number in the cassette associated with the RDS number of the request.
//
// Output:
//  Response  - Wafer map image associated with RDSNo and WaferNo in PDF format. 
//
// Posts a wafer map image request to the WAFER_IMAGE_REQUESTS database table, which is then 
// processed on the EC server by the Process Wafer Image Requests service.
//----------------------------------------------------------------------------------------------------------------------
Service PostWaferImageRequest(RDSNo, WaferNo, TencorRecipe)

    If ( (RDSNo NE '') and (WaferNo NE '') and (TencorRecipe NE '') ) then
        ResponseBody                                    = ''
        RequestDate                                     = Date()
        RequestTime                                     = Time()
        RequestKeyID                                    = RequestDate : '*' : RequestTime
        RequestRow                                      = ''
        RequestRow<WAFER_IMAGE_REQUESTS.RDS_NO$>        = RDSNo
        RequestRow<WAFER_IMAGE_REQUESTS.WAFER_NO$>      = WaferNo
        RequestRow<WAFER_IMAGE_REQUESTS.TENCOR_RECIPE$> = TencorRecipe
        RequestRow<WAFER_IMAGE_REQUESTS.RETURN_PDF$>    = False$
        Database_Services('WriteDataRow', 'WAFER_IMAGE_REQUESTS', RequestKeyID, RequestRow, False$, False$, False$)
    end else
        Error_Services('Add', 'Null parameter passed into service call. All parameters are required.')
    end

return


//----------------------------------------------------------------------------------------------------------------------
// GetWaferImage
//
// Input:
//  Username  - Username of user whom is posting the request.
//  RDSNo     - RDS number associated with the desired wafer image.
//  WaferNo   - Wafer number in the cassette associated with the RDS number of the request.
//
// Output:
//  Response  - Wafer map image associated with RDSNo and WaferNo in PDF format. 
//
// Posts a wafer map image request to the WAFER_IMAGE_REQUESTS database table, which is then 
// processed on the EC server by the Process Wafer Image Requests service.
//----------------------------------------------------------------------------------------------------------------------
Service GetWaferImage(RDSNo, WaferNo, TencorRecipe)

    If (RDSNo NE '') and (WaferNo NE '') and (TencorRecipe NE '') then
        ResponseBody                                    = ''
        RequestDate                                     = Date()
        RequestTime                                     = Time()
        RequestKeyID                                    = RequestDate : '*' : RequestTime
        RequestRow                                      = ''
        RequestRow<WAFER_IMAGE_REQUESTS.RDS_NO$>        = RDSNo
        RequestRow<WAFER_IMAGE_REQUESTS.WAFER_NO$>      = WaferNo
        RequestRow<WAFER_IMAGE_REQUESTS.TENCOR_RECIPE$> = TencorRecipe
        RequestRow<WAFER_IMAGE_REQUESTS.RETURN_PDF$>    = True$
        Database_Services('WriteDataRow', 'WAFER_IMAGE_REQUESTS', RequestKeyID, RequestRow, False$, False$, False$)
        If Error_Services('NoError') then
            TimeExpired = False$
            Start       = GetTickCount()
            Loop
                RequestRow      = Database_Services('ReadDataRow', 'WAFER_IMAGE_REQUESTS', RequestKeyID)
                ResponseDate    = RequestRow<WAFER_IMAGE_REQUESTS.RESPONSE_DATE$>
                If ResponseDate NE '' then
                    ResponseBody = RequestRow<WAFER_IMAGE_REQUESTS.RESPONSE_BODY$>
                    If ResponseBody EQ '' then Error_Services('Add', 'Wafer image not found.')
                end
                // Time will expire after 30 seconds.
                If GetTickCount() - Start GE 30000 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', 'Timed out attempting to retrieve wafer image.')
            end
            Database_Services('DeleteDataRow', 'WAFER_IMAGE_REQUESTS', RequestKeyID, True$)
        end
        
        If ResponseBody NE '' then
            ResponseBody = SRP_Decode(ResponseBody)
        end
        Response    = ResponseBody
    end else
        Error_Services('Add', 'Null parameter passed into service call. All parameters are required.')
        Response    = ''
    end

return


Service ProcessWaferImageRequests()

    hSysLists   = Database_Services('GetTableHandle', 'SYSLISTS')
    Lock hSysLists, ServiceKeyID then
        hWaferImageRequests = Database_Services('GetTableHandle', 'WAFER_IMAGE_REQUESTS')
        If Error_Services('NoError') then
            Sentence    = 'SELECT WAFER_IMAGE_REQUESTS 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
                RequestDate = Field(RequestKeyID, '*', 1)
                RequestTime = Field(RequestKeyID, '*', 2)
                RequestTime = RequestTime / 86400
                RequestTime = RequestTime[3, 5]
                RequestDTM  = RequestDate:'.':RequestTime
                CurrDTM     = Datetime()               
                TimeInQueue = CurrDTM - RequestDTM
                If TimeInQueue LT 1 then
                    Lock hWaferImageRequests, RequestKeyID then
                        EncodedPDF   = ''
                        ResponseBody = ''
                        RequestRow   = Database_Services('ReadDataRow', 'WAFER_IMAGE_REQUESTS', RequestKeyID)
                        RDSNo        = RequestRow<WAFER_IMAGE_REQUESTS.RDS_NO$>
                        WaferNo      = RequestRow<WAFER_IMAGE_REQUESTS.WAFER_NO$>
                        TencorRecipe = RequestRow<WAFER_IMAGE_REQUESTS.TENCOR_RECIPE$>
                        ReturnPDF    = RequestRow<WAFER_IMAGE_REQUESTS.RETURN_PDF$>
                        Reactor      = Xlate('RDS', RDSNo, 'REACTOR', 'X')
                        PSN          = Xlate('RDS', RDSNo, 'PROD_SPEC_ID', 'X')
                        // Format Wafer Number for SQL Query
                        WaferNo = Fmt(TrimF(WaferNo), 'R(0)#2')
                        Query = "DECLARE @RDS         varchar(10) "                                                     |
                              : "DECLARE @RECIPE      varchar(30) "                                                     |
                              : "DECLARE @WFRID       varchar(10) "                                                     |
                              : "SET @RDS      = '":RDSNo:"' "                                                          |
                              : "SET @RECIPE   = '":TencorRecipe:"' "                                                   |
                              : "SET @WFRID    = '":WaferNo:"' "                                                        |
                              : "SELECT TOP (1) child.AttachmentID, child.insertDate "                                  |
                              : "FROM Metrology.dbo.TencorRunHeader header "                                            |
                              : "INNER JOIN Metrology.dbo.TencorRunData child on header.id = child.headerid "           |
                              : "WHERE header.rds = @RDS and header.recipe like @RECIPE + '%' and child.slot = @WFRID " |
                              : "ORDER BY header.insertDate DESC, child.slot ASC"
                              
                        WaferMapDB   = Environment_Services('GetMetrologyProductionPath')     
                        SQLResponse  = SQL_Services('GetDataRows', 'SPC', Query)
                        AttachmentID = SQLResponse<1>
                        InsertDTM    = SQLResponse<2>
                        AttachmentID = SRP_Trim(AttachmentID, 'FB', '{}')
                        WaferMapRoot = Environment_Services('GetWaferMapProductionPath')
                        InsertDate   = InsertDTM[1, 'F ']
                        Year         = Field(InsertDate, '/', 3, 1) 
                        WorkWeek     = 'WW':Date_Services('GetWeekNum', InsertDate)
                        PDFPath      = WaferMapRoot:'\':Year:'\':WorkWeek:'\':AttachmentID:'\image.pdf'
                        Set_Status(0)
                        PDFFile      = ''
                        OSRead PDFFile from PDFPath then
                            // "Cache" PDF on app server
                            QA_Services('PostToWaferImageQueue', RDSNo, WaferNo, TencorRecipe, PDFFile)
                            If ReturnPDF EQ True$ then
                                ResponseBody                                    = SRP_Encode(PDFFile)
                                RequestRow<WAFER_IMAGE_REQUESTS.RESPONSE_BODY$> = ResponseBody
                                RequestRow<WAFER_IMAGE_REQUESTS.RESPONSE_DATE$> = Date()
                                RequestRow<WAFER_IMAGE_REQUESTS.RESPONSE_TIME$> = Time()
                                Database_Services('WriteDataRow', 'WAFER_IMAGE_REQUESTS', RequestKeyID, RequestRow, True$)    
                            end else
                                Database_Services('DeleteDataRow', 'WAFER_IMAGE_REQUESTS', RequestKeyID, True$)
                            end
                        end else
                            // Log failure to find wafer image in the new wafer map path only if the request is over
                            // 30 minutes old.
                            TimeElapsed    = CurrDTM - RequestDTM
                            // (30 minutes * (60 seconds / 1 minute) )
                            HalfHourInSecs = 30 * 60
                            // Convert seconds to internal datetime format ( (24 hours/day) * (60 minutes/hour) * 60 seconds )
                            HalfHourInDTM  = HalfHourInSecs / (24 * 60 * 60)
                            If TimeElapsed GT HalfHourInDTM then
                                LogData    = ''
                                LogData<1> = LoggingDTM
                                LogData<2> = RDSNo
                                LogData<3> = PDFPath
                                Logging_Services('AppendLog', objWaferMapLog, LogData, @RM, @FM)
                            end                            
                            ErrorCode = Status()
                            Error_Services('Add', 'Error retrieving wafer map image for AttachmentID ':AttachmentID)
                        end
                        Unlock hWaferImageRequests, RequestKeyID else Null
                    end
                end else
                    // Delete request because wafer image may never become available.
                    Database_Services('DeleteDataRow', 'WAFER_IMAGE_REQUESTS', RequestKeyID, True$)
                end
            Repeat
        end
        Unlock hSysLists, ServiceKeyID else Null    
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// PostToWaferImageQueue
//
// Input:
//  RDSNo     - RDS number associated with the desired wafer image.
//  WaferNo   - Wafer number in the cassette associated with the RDS number of the request.
//  PDFData   - Raw PDF Data to be written to the App server's local storage.
//
// Posts a wafer map image pdf to the WAFER_IMAGE_QUEUE database table, which is then 
// processed on the App server by the Process Wafer Queue service.
//----------------------------------------------------------------------------------------------------------------------
Service PostToWaferImageQueue(RDSNo, WaferNo, TencorRecipe, PDFData)
    
    If (PDFData NE '') and (RDSNo NE '') and (WaferNo NE '') and (TencorRecipe NE '') then
        KeyID   = RDSNo : '*' : WaferNo : '*' : TencorRecipe
        DataRow = ''
        PDFData = SRP_Encode(PDFData)
        DataRow<WAFER_IMAGE_QUEUE.PDF_DATA$> = PDFData
        Database_Services('WriteDataRow', 'WAFER_IMAGE_QUEUE', KeyID, DataRow, True$)
        If Error_Services('HasError') then
            Error_Services('Add','Failed to write ':KeyID:' to the WAFER_IMAGE_QUEUE table in the ':Service:' service.')
        end
    end else
        ErrorMsg = 'One or more null variables passed into function. All parameters are required to not be null.'
        Error_Services('Add', ErrorMsg)
    end
    
return


//----------------------------------------------------------------------------------------------------------------------
// ProcessWaferImageQueue
//
// This service is to be run on the App server within the Service Manager. It's purpose is to save wafer map PDFs
// locally to the App server. The wafer map PDFs are only available on the EC domain from Sharepoint and as a result
// they must be transmitted to the App server using OpenInsight. Storing the wafer map PDFs locally allows for a
// responsive user interface.
//----------------------------------------------------------------------------------------------------------------------
Service ProcessWaferImageQueue()
    
    hValidationRequests = Database_Services('GetTableHandle', 'WAFER_IMAGE_QUEUE')
    If Error_Services('NoError') then
        Sentence    = "SELECT WAFER_IMAGE_QUEUE WITH PDF_DATA NE ''"
        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', 'WAFER_IMAGE_QUEUE', RequestKeyID)
                If RequestRow NE '' then
                    RDSNo           = Field(RequestKeyID, '*', 1)
                    WaferNo         = Field(RequestKeyID, '*', 2)
                    TencorRecipe    = Field(RequestKeyID, '*', 3)
                    WaferPDF        = RequestRow<WAFER_IMAGE_QUEUE.PDF_DATA$>
                    WaferPDF        = SRP_Decode(WaferPDF)
                    If WaferPDF NE '' then
                        OIAppPath      = Environment_Services('GetApplicationRootPath')
                        WaferImagePath = OIAppPath:'\Wafer_Images\'
                        If TencorRecipe EQ '' then
                            FileName       = 'WaferMap_RDSNo_':RDSNo:'_WaferNo_':WaferNo:'.pdf'
                        end else
                            FileName       = 'WaferMap_RDSNo_':RDSNo:'_Recipe_':TencorRecipe:'_WaferNo_':WaferNo:'.pdf'
                        end
                        Status         = ''
                        Set_Status(0)
                        OSWrite WaferPDF to WaferImagePath:FileName
                        Status         = Get_Status()
                    end
                end
                Database_Services('DeleteDataRow', 'WAFER_IMAGE_QUEUE', RequestKeyID, True$)
                Unlock hValidationRequests, RequestKeyID else Null
            end
        Repeat
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// ROTRImpactReport
//
// Input: None
//
// The purpose of this service is to provide management with an on demand report to see how the production floor
// would be impacted by the tighter process controls introduced when using the current ROTR parameters for each reactor.
//----------------------------------------------------------------------------------------------------------------------
Service ROTRImpactReport()

    Server      = Environment_Services('GetServer')
    If (Server NE 'MESST5201') and (Server NE 'MESST5202') then
        LogPath     = Environment_Services('GetReportsRootPath')
        LogDate     = Oconv(Date(), 'D4/')
        LogTime     = Oconv(Time(), 'MTS')
        LogFileName = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' ROTR Impact Analysis.csv'
        Headers = 'Reactor No,Reactor Load Blocked,ROTR Fail Reason,# of 100% Inspections,Total NCR Count,' |
                : 'Zero NCR Count,UCL Exceeded Count,Run Count,# of Override Sigs,# of Block Override Sigs,' |
                : 'Reactor Yield %'
        Swap ',' with @FM in Headers
        objLog      = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, Comma$, Headers, '', False$, True$)
    end

    ReactorList = Database_Services('ReadDataRow', 'SYSLISTS', 'REACTOR_LIST')
    ReactorList = Delete(ReactorList, 1, 0, 0)
    ReactorBlockArray = ''
    ReactorCnt = DCount(ReactorList, @FM)
    Def = ""
    Def<MCAPTION$>      = "Building ROTR Impact Analysis Report..."
    Def<MTYPE$>         = "GC"
    Def<MEXTENT$>       = ReactorCnt
    Def<MTEXTWIDTH$>    = 600
    // Start progress meter
    MsgUp = Msg(@WINDOW, Def)
    
    For each Reactor in ReactorList setting ReactorIndex
        ReactorType = Xlate('REACTOR', Reactor, 'REACT_TYPE', 'X')
        If (ReactorType EQ 'ASM+' OR ReactorType EQ 'HTR' OR ReactorType EQ 'ASM') then
            ReactorBlockArray<ReactorIndex, 1>  = Reactor
            ReactorBlockArray<ReactorIndex, 2>  = ''       ; // Reactor Load Blocked
            ReactorBlockArray<ReactorIndex, 3>  = ''       ; // ROTR Failure Reason
            ReactorBlockArray<ReactorIndex, 4>  = 0        ; // Num Runs requiring 100% scans
            ReactorBlockArray<ReactorIndex, 5>  = 0        ; // NCRCount
            ReactorBlockArray<ReactorIndex, 6>  = 0        ; // ZeroNCRCount
            ReactorBlockArray<ReactorIndex, 7>  = 0        ; // UCLExceededCount
            ReactorBlockArray<ReactorIndex, 8>  = 0        ; // Run Count
            ReactorBlockArray<ReactorIndex, 9>  = 0        ; // Num Override Sigs (blocking + non-blocking events)
            ReactorBlockArray<ReactorIndex, 10> = 0        ; // Num Block Override Sigs (e.g. NCR Limit Exceeded)
            ReactorBlockArray<ReactorIndex, 11> = 0        ; // Reactor Yield %
            CurrRDS = Xlate('REACT_STATE', Reactor, 'LAST_RDS_NO', 'X')
            LWICIKey = Xlate('RDS', CurrRDS, 'LWI_CI_NO', 'X')
            CIRec = Database_Services('ReadDataRow', 'CLEAN_INSP', LWICIKey)
            
            If Error_Services('NoError') then
            
                SigRequired      = CIRec<CLEAN_INSP_SPEC_SURFSCAN_REQ$>
                ScanSigs         = CIRec<CLEAN_INSP_SCAN_SIG$>
                // Get the specifications
                SpecRecipes      = CIRec<CLEAN_INSP_SPEC_SURFSCAN_RECIPE$>
                SpecDefects      = CIRec<CLEAN_INSP_SPEC_SURF_DEFECTS$> ; // Spec USL
                SpecHazes        = CIRec<CLEAN_INSP_SPEC_SURF_HAZE$>
                // Get the scanned values
                ScanRecipes      = CIRec<CLEAN_INSP_SCAN_RECIPE$>
                ScanDefectsAvg   = CIRec<CLEAN_INSP_SCAN_SUM_OF_DEF_AVG$>
                ScanDefects      = CIRec<CLEAN_INSP_SCAN_SUM_OF_DEF_MAX$>
                ScanHazes        = CIRec<CLEAN_INSP_SCAN_HAZE_AVG_AVG$>
                // Get the individual SOD values per wafer.
                SODPerWafer      = CIRec<CLEAN_INSP_SCAN_SOD_PER_WAFER$>
                // Get the fail scan flags so that we can get/set them as necessary.
                FailScanFlags    = CIRec<CLEAN_INSP_FAIL_SCAN_REQ$>
                ScanMismatch     = CIRec<CLEAN_INSP_SCAN_RECIPE_MISMATCH$>
                NumFailedWfrs    = CIRec<CLEAN_INSP_NUM_FAILED_WAFERS$>
                
                NumScanRecipes   = DCount(ScanRecipes, @VM)
                ROTRAction       = ''   ; // Assume it passes until otherwise proven.
                ROTRActionReason = ''
                CIReactor        = Reactor
                // Check if a mismatching recipe name has been set.
                If (ScanMismatch EQ '') then
                    // ROTR Reactor/PSN Status Health Check - Uses parameters set within the ROTR Parameters form.
                    // We only need to run this once per CLEAN_INSP record.
                    RDSKey               = CurrRDS
                    ROTRStatus           = 'P'   ; // Assume pass until proven otherwise.                  
                    ROTRResponse         = QA_Services('GetROTRStatus', RDSKey)
                    ROTRStatus           = ROTRResponse<1>
                    ROTRFailReason       = ROTRResponse<2>
                    NCRCount             = ROTRResponse<3>
                    ZeroNCRCount         = ROTRResponse<4>
                    UCLExceededCount     = ROTRResponse<5>
                    RunCount             = ROTRResponse<6>
                    NumOverrideSigs      = ROTRResponse<7>
                    NumBlockOverrideSigs = ROTRResponse<8>
                    ReactorYield         = ROTRResponse<9>
                    ReactorBlockArray<ReactorIndex, 5>  = NCRCount
                    ReactorBlockArray<ReactorIndex, 6>  = ZeroNCRCount
                    ReactorBlockArray<ReactorIndex, 7>  = UCLExceededCount
                    ReactorBlockArray<ReactorIndex, 8>  = RunCount
                    ReactorBlockArray<ReactorIndex, 9>  = NumOverrideSigs
                    ReactorBlockArray<ReactorIndex, 10> = NumBlockOverrideSigs
                    ReactorBlockArray<ReactorIndex, 11> = ReactorYield

                    // Check each spec recipe. There must be at least one scan recorded for each spec recipe.
                    For Each SpecRecipeName in SpecRecipes using @VM setting SpecRecipeIndex
                        // Look for the last scan entry related to the current spec recipe.
                        RecipeFound = False$
                        For ScanRecipeIndex = NumScanRecipes To 1 Step -1
                            ScanRecipeName  = ScanRecipes<0, ScanRecipeIndex>
                            RecipeFound     = (ScanRecipeName EQ SpecRecipeName)
                        Until RecipeFound
                        Next ScanRecipeIndex
                        // If a scan was found, then analyze the scan data for spec requirements. If not found, this
                        // automatically fails.
                        If RecipeFound EQ True$ then
                            SpecDefect          = Oconv(SpecDefects<0, SpecRecipeIndex>, 'MD0')
                            SpecHaze            = Oconv(SpecHazes<0, SpecRecipeIndex>,   'MD2')
                            ScanDefect          = Oconv(ScanDefects<0, ScanRecipeIndex>, 'MD0')
                            ScanHaze            = Oconv(ScanHazes<0, ScanRecipeIndex>,   'MD3')
                            ScanSig             = ScanSigs<0, ScanRecipeIndex>
                            ScanFailedWfrs      = NumFailedWfrs<0, ScanRecipeIndex>
                            FailScanReq         = FailScanFlags<0, ScanRecipeIndex>                        
                            FailureScan         = False$
                            ScanSODPerWafer     = SODPerWafer<0, ScanRecipeIndex>
                            WaferCount          = 0 
                            NumFailedWafers     = 0
                            For each WaferSOD in ScanSODPerWafer using @SVM setting WaferIndex
                                If WaferSOD NE '' then
                                    // We have SOD data for this wafer so check if it is < SOD Max
                                    If WaferSOD LE SpecDefect then
                                        WaferCount += 1
                                    end else
                                        NumFailedWafers += 1
                                    end
                                end
                            Next WaferSOD
                            ReactorBlockArray<ReactorIndex, 4> = NumFailedWafers                                      
                            // We need to check if reactor recently underwent ROTR maintenance. If so, then
                            // we must also check if this RDS is one of the first two runs after maintenance.
                            // If this is the case, then the RDS must undergo a 100% scan.                            
                            MaintScanReq = QA_Services('GetMaintenanceScanStatus', RDSKey)
                            FailScanReq  = (FailScanReq or MaintScanReq)
                            // Search for '100' in the recipe name to determine if this is a failure scan.
                            FailureScan = Index(ScanRecipeName, '100', 1)
                            If FailureScan > 0 then FailureScan = True$
                            If (FailScanReq EQ True$) and (FailureScan EQ True$) then
                                // Fail scan was required and submitted, so turn off the flag.
                                FailScanReq = False$
                                FailScanFlags<0, ScanRecipeIndex> = False$
                            end
                            Begin Case
                                Case (ROTRStatus EQ 'F')
                                    // Check this case first as to not miss blocking the reactor if necessary.
                                    // Block load signature on this reactor for future lots until overriden at Load button
                                    // signature click event by supervisor, lead, or engineer.
                                    ReactorBlockArray<ReactorIndex, 2> = 'Yes'
                                    ReactorBlockArray<ReactorIndex, 3> = ROTRFailReason
                                Case (ScanDefect GT SpecDefect)
                                    ROTRFailLimit   = Xlate('REACTOR', CIReactor, 'ROTR_FAIL_LIMIT', 'X')
                                    // If a Post Clean is required, then disregard the ROTRFailLimit per Tom Tillery.
                                    CleanReq        = False$
                                    PSNo            = Xlate('CLEAN_INSP', LWICIKey, 'PS_NO', 'X')
                                    PRSStages = Xlate('PROD_SPEC', PSNo, 'PRS_STAGE_KEY', 'X')
                                    If Index(PRSStages, 'POST', 1) then
                                        CleanReq = Xlate('PRS_STAGE', PSNo:'*POST', 'CLEAN_SIG_REQ', 'X') 
                                    end                                 
                                    If (NumFailedWafers LT ROTRFailLimit) or (CleanReq EQ True$) or (FailureScan EQ True$) then
                                        Null
                                    end else
                                        FailScanReq         = True$
                                        FailScanFlags<0, ScanRecipeIndex> = FailScanReq
                                        FailScanCnt         = ReactorBlockArray<ReactorIndex, 4>
                                        ReactorBlockArray<ReactorIndex, 4> = FailScanCnt + 1
                                    end
                                Case (FailScanReq EQ True$) and (FailureScan EQ False$)
                                    If MaintScanReq EQ True$ then
                                        Null
                                    end else
                                        // If ROTR maintenance flag not set, then wafer fail limit must have been met in
                                        // an earlier Tencor run. We must set it here again due to the "assume pass until
                                        // fail design of this MFS."
                                        FailScanReq      = True$
                                        FailScanFlags<0, ScanRecipeIndex> = FailScanReq
                                        FailScanCnt         = ReactorBlockArray<ReactorIndex, 4>
                                        ReactorBlockArray<ReactorIndex, 4> = FailScanCnt + 1                                 
                                    end
                                Case Otherwise$
                                    // This scan data is within spec, check next recipe.
                            End Case                           
                        end                   
                    Next SpecRecipeName
                end
            end
        end else
            // ROTR not currently implemented for EpiPro reactors.
            ReactorBlockArray<ReactorIndex, 1> = Reactor
            ReactorBlockArray<ReactorIndex, 2> = 'N/A' ; // ROTR Status
        end
        
        If (Server NE 'MESST5201') and (Server NE 'MESST5202') then
            LogData      = ''
            LogData<1>   = ReactorBlockArray<ReactorIndex, 1>  ; // Reactor No
            LogData<2>   = ReactorBlockArray<ReactorIndex, 2>  ; // Reactor Load Blocked
            LogData<3>   = ReactorBlockArray<ReactorIndex, 3>  ; // ROTR Failure Reason
            LogData<4>   = ReactorBlockArray<ReactorIndex, 4>  ; // Num Runs requiring 100% scans 
            LogData<5>   = ReactorBlockArray<ReactorIndex, 5>  ; // NCRCount 
            LogData<6>   = ReactorBlockArray<ReactorIndex, 6>  ; // ZeroNCRCount
            LogData<7>   = ReactorBlockArray<ReactorIndex, 7>  ; // UCLExceededCount
            LogData<8>   = ReactorBlockArray<ReactorIndex, 8>  ; // Run Count
            LogData<9>   = ReactorBlockArray<ReactorIndex, 9>  ; // Num Override Sigs
            LogData<10>  = ReactorBlockArray<ReactorIndex, 10> ; // Num Blocking Override Sigs
            ReactorYield = ReactorBlockArray<ReactorIndex, 11> ; // Reactor Yield
            If ReactorYield NE '' then
                LogData<11>   = (ReactorYield * 100) : '%'
            end else
                LogData<11>   = ReactorYield
            end
            Logging_Services('AppendLog', objLog, LogData, @RM, @FM, True$)
        end
    
    // Update progress meter, check for cancel input.
    While Msg(@WINDOW, MsgUp, ReactorIndex, MSGINSTUPDATE$)
    Next Reactor
    
    If (Server NE 'MESST5201') and (Server NE 'MESST5202') then
        ReportMsg    = ''
        ReportMsg<MTEXT$> = 'ROTR Impact Analysis.csv saved to your local OIReports folder.'
        Msg(@Window, ReportMsg)
    end
    
    // Take down progress meter message.
    Msg(@WINDOW, MsgUp)

    If (Server NE 'MESSA005') and (Server NE 'MESSA01EC') then
        // Try to create Excel spreadsheet for the user
        eXcelCols = 'A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,AA,BB,CC,DD,EE,FF,GG,HH,II,JJ,KK,LL'
        Swap "','" with ',' in eXcelCols
        Convert ',' to @VM in eXcelCols
        xlApp = OleCreateInstance("excel.Application")                       ;* Create instance of Excel
        If Not(OleStatus()) then 
            OlePutProperty(XlApp, 'Visible', xlSheetVisible)
            xlWorkBooks =OleGetProperty(xlApp, "Workbooks")                  ;* Create workbook
            xlWkb = OleCallMethod(xlWorkbooks, "Add")
            xlSht = OleGetProperty(xlWkb, "Worksheets", 1)                   ;* Select worksheet 1
            
            DataArray = ReactorBlockArray
            FirstRow = DataArray<1>       
            ColCnt = DCount(FirstRow, @VM)
            Header = 'Reactor No,Reactor Load Blocked,ROTR Fail Reason,# of 100% Inspections,Total NCR Count,' |
                   : 'Zero NCR Count,UCL Exceeded Count,Run Count,# of Override Sigs,# of Block Override Sigs,' |
                   : 'Reactor Yield %'
            Swap ',' with @FM in Header
            DataArray = Insert(DataArray, 0, 0, 0, '')
            LineCnt = DCount(DataArray, @FM)
            For Col = 1 to ColCnt
                DataArray = Insert(DataArray, 1, Col, 0, Header<Col>)
            Next Col
            FOR LineNo = 1 to LineCnt
              FOR Column = 1 to ColCnt
                  RangeColumn = eXcelCols<1,Column>
                  range = OleGetProperty( xlSht, 'Range',RangeColumn:LineNo)
                  OlePutProperty(range, 'Value', DataArray<LineNo, Column>)  ;* Load report array into spreadsheet cells
                  OlePutProperty(range, 'HorizontalAlignment', -4108)        ;* Set cell alignment to centered
              NEXT Column
            NEXT LineCnt
            
            column = OleGetProperty( xlSht, 'Range' , 'A:A' )       
            OlePutProperty( column , 'ColumnWidth' , '10' )
            column = OleGetProperty( xlSht, 'Range' , 'B:B' )       
            OlePutProperty( column , 'ColumnWidth' , '20' )
            column = OleGetProperty( xlSht, 'Range' , 'C:C' )       
            OlePutProperty( column , 'ColumnWidth' , '35' )
            column = OleGetProperty( xlSht, 'Range' , 'D:D' )       
            OlePutProperty( column , 'ColumnWidth' , '22' )
            column = OleGetProperty( xlSht, 'Range' , 'E:E' )       
            OlePutProperty( column , 'ColumnWidth' , '15' )
            column = OleGetProperty( xlSht, 'Range' , 'F:F' )       
            OlePutProperty( column , 'ColumnWidth' , '15' )
            column = OleGetProperty( xlSht, 'Range' , 'G:G' )       
            OlePutProperty( column , 'ColumnWidth' , '20' )
            column = OleGetProperty( xlSht, 'Range' , 'H:H' )       
            OlePutProperty( column , 'ColumnWidth' , '10' )
            column = OleGetProperty( xlSht, 'Range' , 'I:I' )       
            OlePutProperty( column , 'ColumnWidth' , '20' )
            column = OleGetProperty( xlSht, 'Range' , 'J:J' )       
            OlePutProperty( column , 'ColumnWidth' , '25' )
            column = OleGetProperty( xlSht, 'Range' , 'K:K' )
            OlePutProperty( column , 'ColumnWidth' , '15' )
            OlePutProperty( column , 'NumberFormat' , '0.0%' )     
        end
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// CleanWaferImageRepo
//
// Input: NumDays - The number of days which determines whether or not a file will be deleted.
//
// Removes wafer image files from the wafer images folder that are more than the desired number of days old.
// Note that users can still access wafer images that are deleted, however OI will have retrieve them from Sharepoint
// again. Wafer images are automatically pulled from Sharepoint and stored locally for quick access from OI.  
//----------------------------------------------------------------------------------------------------------------------
Service CleanWaferImageRepo(NumDays)

    RepoPath = Environment_Services('GetApplicationRootPath') : '\Wafer_Images\'
    InitDir RepoPath:'*.pdf'
    FileList = DirList()
    Today    = Date()
    For each Filename in FileList
        FileInfo      = Dir(RepoPath:Filename)
        LastWriteDate = FileInfo<2>
        FileAge       = Today - LastWriteDate
        If FileAge GT NumDays then
            OSDelete RepoPath:Filename
        end
    Next Filename
    
end service


//----------------------------------------------------------------------------------------------------------------------
// PreEpiSignReady
//
// Input:
//  RDSNo     - RDS to be signed
//  Username  - LSL Username of the operator signing the stage.
//  WaferQty  - User supplied wafer quantity to verify against the schedule.
//  Reactor   - Reactor to assign to the RDS record.
//
// Output:
//  Reponse   - True$ if PRE stage is ready to be signed (all preconditions met and no errors encountered),
//              False$ otherwise.
//----------------------------------------------------------------------------------------------------------------------
Service PreEpiSignatureReady(RDSNo, Username, WaferQty, Reactor)
	StartTick = GetTickCount()
    MetricName = 'PreEpiSignatureReady'

    If (RDSNo EQ '') or (Username EQ '') or (WaferQty EQ '') or (Reactor EQ '') then
        ErrorMessage = 'RDSNo, Username, WaferQty, or Reactor not supplied in the ':Service:' service.'
        Error_Services('Set', ErrorMessage)
        Response = False$
        return
    end     
    
    RDSRec = Database_Services('ReadDataRow', 'RDS', RDSNo)
    If Error_Services('HasError') then
        ErrorMessage = 'Error reading RDS record: ':RDSNo:' in the ':Service:' service.'
        Error_Services('Set', ErrorMessage)
        Response = False$
        return
    end
    
    WONo        = RDSRec<RDS_WO$>
    CassNo      = RDSRec<RDS_CASS_NO$>
    WOStep      = Xlate('RDS', RDSNo, 'WO_STEP', 'X')
    PreEpiSig   = RDSRec<RDS_PRE_EPI_SIG$>
    ReactorType = Xlate('RDS', RDSNo, 'REACTOR_TYPE', 'X')
    SchedQty    = RDSRec<RDS_CASS_WAFER_QTY$>
  
    If WaferQty NE SchedQty then
        ErrorMessage = 'The verified wafer quantity does not match the scheduled quantity.'
        Error_Services('Set', ErrorMessage)
        Response = False$
        return
    end
       
    If PreEpiSig NE '' THEN
        ErrorMessage = 'Process Error: Pre Epi stage has already been signed.'
        Error_Services('Set', ErrorMessage)
        Response = False$
        return
    end
    
    WOMatCurrStatus = obj_WO_Mat('CurrStatus',WONo:'*':CassNo)
    IF WOMatCurrStatus EQ 'HOLD' THEN
        ErrorMessage = 'Process Error: Cassette is on Hold and may not be signed off.'
        Error_Services('Set', ErrorMessage)
        Response = False$
        return
    end
                                                   
    IF ReactorType NE 'EPP' THEN
        WOMatLocs = XLATE('WO_MAT', WONo:'*':CassNo, WO_MAT_INV_LOCATION$, 'X')
        IF NOT(INDEX(WOMatLocs,'PTI',1)) THEN
            ErrorMessage = 'Process Error: Cassette has not been scanned through the Passthrough!'
            Error_Services('Set', ErrorMessage)
            Response = False$
            return
        END                           
    END
    
    PREInstAckReq  = Xlate('RDS', RDSNo, 'PRE_INST_ACK_REQ', 'X')
    IF PREInstAckReq EQ True$ THEN
        ErrorMessage = 'The pre epi stage engineering instructions must be acknowledged before the load operation can be signed.'
        Error_Services('Set', ErrorMessage)
        Response = False$
        return                              
    END
    
    Query         = 'SELECT REACT_STATE WITH SCHED_WO CONTAINING ':WONo
    ClearSelect
    Set_Status(0)
    RList(Query, TARGET_ACTIVELIST$, '', '' '')
    SchedReactors = ''
    EOF           = False$
    Loop
        ReadNext SchedReactor else EOF = True$
    Until EOF
        SchedReactors<0, -1> = SchedReactor
    Repeat
    
	Locate Reactor in SchedReactors using @VM setting vPos else
		Swap @VM with ',' in SchedReactors
		ErrorMessage = 'Assigned reactor, ':Reactor:', does not equal any scheduled reactor(s), ':SchedReactors:'.'
		Error_Services('Set', ErrorMessage)
		Response     = False$
		return 
	end

    // Add check for supplement signatures
    UnacknowledgedSupp = Supplement_Services('UnacknowledgedSupplementCheck', 'RDS', RDSNo, 'VER')
    If UnacknowledgedSupp NE FALSE$ then
        ErrorMessage = 'The pre epi stage supplements must be acknowledged before the load operation can be signed.'
        Error_Services('Set', ErrorMessage)
        Response = False$
        return    
    end
    
    If Error_Services('NoError') then
        Response = True$
    end else
        Response = False$
    end
    
    EndTick = GetTickCount()
    Mona_Services('QueueLatencyAndCountMetrics', MonaResource, MetricName, StartTick, EndTick)    
end service


//----------------------------------------------------------------------------------------------------------------------
// SignPreEpiStage
//
// Input:
//  RDSNo     - RDS to be signed
//  Username  - LSL Username of the operator signing the stage.
//  WaferQty  - User supplied wafer quantity to verify against the schedule.
//  Reactor   - Reactor to assign to the RDS record.
//
// Output:
//  Reponse   - True$ if PRE stage was successfully signed, False$ otherwise.
//----------------------------------------------------------------------------------------------------------------------
Service SignPreEpiStage(RDSNo, Username, WaferQty, Reactor, ScanEntry)
	
	StartTick = GetTickCount()
    MetricName = 'SignPreEpiStage'

    Error_Services('Clear')
    If (RDSNo EQ '') or (Username EQ '') or (WaferQty EQ '') or (Reactor EQ '') then
        ErrorMessage = 'RDSNo, Username, or WaferQty not supplied in the ':Service:' service.'
        Error_Services('Add', ErrorMessage)
        Response = False$
        return
    end
    
    RDSRec = Database_Services('ReadDataRow', 'RDS', RDSNo)
    If Error_Services('HasError') then
        ErrorMsg = 'Error reading RDS record: ':RDSNo:' in the ':Service:' service.'
        Error_Services('Set', ErrorMsg)
        Response = False$
        return
    end
    
	// Code copied from RDS_PRE_EPI write script event handler

    OrgReactNo = XLATE('RDS', RDSNo, RDS_REACTOR$, 'X')
    
    WONo    = RDSRec<RDS_WO$>
    CassNo  = RDSRec<RDS_CASS_NO$>
    PSNo    = RDSRec<RDS_PROD_SPEC_ID$>
    WOStep  = Xlate('RDS', RDSNo, 'WO_STEP', 'X')

    // Need to update/save RDS record before calling RDS_React_Run
    RDSRec<RDS_REACTOR$>    = Reactor
    RDSRec<RDS_VERIFY_QTY$> = WaferQty
    Database_Services('WriteDataRow', 'RDS', RDSNo, RDSRec, True$, False$, True$)
    
    RDS_React_Run(RDSNo)	;* Conversion of data to REACT_RUN and REACT_RUN_CI & Adds Sig to WO_MAT 3/18/2008 JCH      
    
    PreEpiSig   = RDSRec<RDS_PRE_EPI_SIG$>
    ReactorType = Xlate('RDS', RDSNo, 'REACTOR_TYPE', 'X')
        
	IF ReactorType NE 'EPP' THEN
		
		WOMatKey        = WONo:'*':CassNo
		WOMatRec		= Database_Services('ReadDataRow', 'WO_MAT', WOMatKey)
		WOMatSigProfile = WOMatRec<WO_MAT_SIG_PROFILE$>
		SigAction		= WOStep:'VER'

		LOCATE SigAction IN WOMatSigProfile USING @VM SETTING Pos THEN
			WOMatSig = WOMatRec<WO_MAT_SIGNATURE$,Pos>
			CurrDTM  = OCONV(WOMatRec<WO_MAT_SIG_DTM$,Pos>,'DT/4^HS')
		END ELSE
			WOMatSig = ''
		END
		
		RDSSig = PreEpiSig
		
		BEGIN CASE
			CASE (WOMatSig EQ '') AND (RDSSig NE '')
		
				* Signature missing on WO_MAT                       
				SigDate = RDSRec<RDS_PRE_EPI_SIG_DATE$>
				SigTime = RDSRec<RDS_PRE_EPI_SIG_TIME$>
				owmParms = WONo:@RM:CassNo:@RM:WOStep:@RM:'VER':@RM:RDSSig:@RM:SigDate:' ':SigTime
				* Sets VER signature
				errCode = ''
				IF Get_Status(errCode) THEN
					ErrorMsg = 'Process Error: Error calling Obj_WO_Mat("SetSignature") within ':Service:'. Error code: ':errCode
					Error_Services('Set', ErrorMsg)
					Response = False$
				END else
					Response = True$
				end
				return
						   
			CASE (RDSSig = '') AND (WOMatSig NE '')
			
				* Signature missing on RDS                        
				CurrDate                      = CurrDTM[1,' ']
				CurrTime                      = CurrDTM[COL2()+1,' ']                        
				RDSRec<RDS_PRE_EPI_SIG$>      = WOMatSig
				RDSRec<RDS_PRE_EPI_SIG_DATE$> = IConv(CurrDate, 'D')
				RDSRec<RDS_PRE_EPI_SIG_TIME$> = IConv(CurrTime, 'MT')
				Database_Services('WriteDataRow', 'RDS', RDSNo, RDSRec, True$, False$, True$)
				Response                      = True$                      
				return
				
			CASE RDSSig NE '' AND WOMatSig NE ''
				
				ErrorMessage = 'Process Error: Pre Epi stage has already been signed.'
				Error_Services('Set', ErrorMessage)
				Response = False$                    
				return
				
			CASE Otherwise$
				Null
				
		END CASE
		
	END	;* End of check for Reactor Type   

    SigDate = OCONV( Date(), 'D2/' )
    SigTime	= OCONV( Time(), 'MTS' )

    IF ReactorType NE 'EPP' THEN

        ReactID		= 'R':Reactor	
        ReactWH		= XLATE('REACTOR', Reactor, 'TOOL_WH', 'X')
        ReactLoc	= XLATE('REACTOR', Reactor, 'TOOL_LOC', 'X')
        Tag			= RDSNo

        owmParms = WONo:@RM:CassNo:@RM:WOStep:@RM:'VER':@RM:Username:@RM:SigDate:' ':SigTime	;* Sets VER signature
        
        IF Get_Status(errCode) THEN
            ErrorMessage = 'Process Error: Error calling obj_WO_Mat("SetSignature"). Error code: ':errCode
            Error_Services('Set', ErrorMessage)
            Response = False$
            Return
        END
        
        LogFile	= 'WO_MAT'				; WOMLParms  = LogFile:@RM
        LogDTM	= SigDate:' ':SigTime	; WOMLParms := LogDTM:@RM
        Action	= WOStep:'VER'			; WOMLParms := Action:@RM
        WhCd	= ReactWH				; WOMLParms := WhCd:@RM
        LocCd	= ReactLoc				; WOMLParms := LocCd:@RM
        WONos	= WONo					; WOMLParms := WONos:@RM
        CassNos	= CassNo				; WOMLParms := CassNos:@RM
        UserID	= Username				; WOMLParms := UserID:@RM
        Tags	= ''					; WOMLParms := Tags:@RM
        ToolID	= ReactID				; WOMLParms := ToolID:@RM
        WOMLParms := ScanEntry
        
        obj_WO_Mat_Log('Create',WOMLParms)		;* Add 'VER' event WO_MAT Event Log
        IF Get_Status(errCode) THEN
            ErrorMessage = 'Process Error: Error calling obj_WO_Mat_Log("Create"). Error code: ':errCode
            Error_Services('Set', ErrorMessage)
            Response = False$
            Return
        END
        
    END

    /* Sync up the RDS record with WO_MAT */
    RDSRec<RDS_PRE_EPI_SIG$>      = Username
    RDSRec<RDS_PRE_EPI_SIG_DATE$> = IConv(SigDate, 'D')
    RDSRec<RDS_PRE_EPI_SIG_TIME$> = IConv(SigTime, 'MT')
    Database_Services('WriteDataRow', 'RDS', RDSNo, RDSRec, True$, False$, True$)   
    RunOrderNo  = Xlate('RDS', RDSNo, 'RUN_ORDER_NUM', 'X')
    PSNo        = Xlate('RDS', RDSNo, 'PROD_SPEC_ID', 'X')
    
    // The first run on a reactor requires that RDS Layer parameters be entered manually.
    If RunOrderNo GT 1 then                                                   
    // Wafer is eligible to have recipe parameters automatically applied from the previous cassette.
    // Check to see if parameters have already been populated. This might be because the user manually entered them.
    // If so, do not automatically copy parameters from the previous cassette.
        LSParmsComp     = Xlate('RDS', RDSNo, 'LS_PARMS_COMP', 'X')
        FirstParmsComp  = LSParmsComp<1,1>
        If (FirstParmsComp EQ False$) then RDS_Services('CopyRDSLayerParameters', RDSNo)
    end
    
    If Error_Services('NoError') then
        If RDS_Services('IsEpiPro', RDSNo) then
            LotId = Lot_Services('GetLotIdByLegacyLotIdAndType', RDSNo, 'RDS')
            If Error_Services('NoError') then
                Lot_Services('MoveOutLot', LotId, Username) ; // Move out of PRE_EPI operation
                Lot_Services('MoveInLot', LotId, Username) ; // Move into REACTOR_RUN operation
            end
        end
    end
    
    If Error_Services('NoError') then
        Response = True$
    end else
        Response = False$
    end
    
    EndTick = GetTickCount()
    Mona_Services('QueueLatencyAndCountMetrics', MonaResource, MetricName, StartTick, EndTick)
    
end service


//----------------------------------------------------------------------------------------------------------------------
// LoadSignatureReady
//
// Input:
//  RDSNo     - RDS to be signed
//  Username  - LSL Username of the operator signing the stage.
//  WaferQty  - User supplied wafer quantity to verify against the schedule.
//  LLSide    - Load lock side to be saved within the RDS record.
//  PreFlag   - Optional parameter. Skip pre-stage check(Used in Scanner)
//  ReactNoOpt - Used in conjunction with above Pre-Flag. If skipping pre-stage check ReactorNo not saved to RDS record so need to pass it through 
//
// Output:
//  Response  - True$ if LOAD stage is ready to be signed (all preconditions met and no errors encountered),
//              False$ otherwise.
//----------------------------------------------------------------------------------------------------------------------
Service LoadSignatureReady(RDSNo, Username, WaferQty, LLSide, PreFlag, ReactNoOpt)
	StartTick = GetTickCount()
    MetricName = 'LoadSignatureReady'
    ErrorMsg = ''
    Error_Services('Clear')
    If (RDSNo EQ '') or (Username EQ '') or (WaferQty EQ '') then
        ErrorMsg = 'Process Error: RDSNo, Username, or WaferQty not supplied in the ':Service:' service.'
        Error_Services('Set', ErrorMsg)
        Response = False$
        return
    end
      
    RDSRec = Database_Services('ReadDataRow', 'RDS', RDSNo)
    If Error_Services('HasError') then
        ErrorMsg = 'Error reading RDS record: ':RDSNo:' in the ':Service:' service.'
        Error_Services('Set', ErrorMsg)
        Response = False$
        return
    end          
    WONo            = RDSRec<RDS_WO$>
    CassNo          = RDSRec<RDS_CASS_NO$>
    Reactor         = RDSRec<RDS_REACTOR$>
    If Reactor EQ '' AND Assigned(ReactNoOpt) AND PreFlag EQ True$ then
    	Reactor = ReactNoOpt
    end
    PSN             = RDSRec<RDS_PROD_SPEC_ID$>
    LoadSig         = RDSRec<RDS_OPERATOR_IN$>
    WOStep          = Xlate('RDS', RDSNo, 'WO_STEP', 'X')
    ReactorType     = Xlate('RDS', RDSNo, 'REACTOR_TYPE', 'X')
    LoadLockVal     = LLSide
    
    TWAllSigned     = Xlate('RDS', RDSNo, RDS_ALL_TW_USE_SIGNED$, 'X')
    If TWAllSigned EQ '' then
    	TWAllSigned = True$
    end
    
    RunOrderNo      = Xlate('RDS', RDSNo, 'RUN_ORDER_NUM', 'X')        
    SchedQty        = RDSRec<RDS_CASS_WAFER_QTY$>
    ParamOutOfSpec  = Xlate('RDS', RDSNo, 'PARAM_OUT_OF_SPEC', 'X')
*    Supplement      = RDSRec<RDS_SUPPLEMENT$>
    Supplement   = Supplement_Services('HasSupplements', 'RDS', RDSNo, 'LOAD')

    ReactorCapacity = Reactor_Services('GetReactorAvailChamberCount', Reactor)
    If ReactorCapacity LE 0 then
    	
    	ErrorMsg = 'Process Error: Reactor ' : Reactor : ' does not have the capacity for this lot.'
        Error_Services('Set', ErrorMsg)
        Response = False$
        return
    end
	
    If WaferQty NE SchedQty then
        ErrorMsg = 'Process Error: Verified wafer quantity does not match the scheduled quantity.'
        Error_Services('Set', ErrorMsg)
        Response = False$
        return
    end
    
    // The first run on a reactor requires that RDS Layer parameters be entered manually. This is when Prove-in
    // occurs.
    If RunOrderNo GT 1 then               
        // Wafer is eligible to have recipe parameters automatically applied from the previous cassette.
        // Check to see if parameters have already been populated. This might be because the user manually entered them.
        // If so, do not automatically copy parameters from the previous cassette.
        LSParmsComp     = Xlate('RDS', RDSNo, 'LS_PARMS_COMP', 'X')
        FirstParmsComp  = LSParmsComp<1,1>
        IF (FirstParmsComp EQ False$) THEN
            RDS_Services('CopyRDSLayerParameters', RDSNo)
        end        
        // If this lot falls upon a test wafer frequency, then we need to prompt the user to confirm
        // the parameters. If they do not confirm them, then we need to stop the load signature.                   
        RDSLayerAckReq = Xlate('RDS', RDSNo, 'RDS_LAYER_ACK_REQ', 'X')
        If RDSLayerAckReq EQ True$ then
            ErrorMsg = 'RDS layer parameters must be reviewed for accuracy and acknowledged before the load operation can be signed.'
            Error_Services('Set', ErrorMsg)
            Response = False$
            return
        end            
    end

    // Ensure recipe parameters (RDS Layer parameters) have been manually entered or automatically applied.
    LSParmsComp = Xlate('RDS', RDSNo, 'LS_PARMS_COMP', 'X')
    Buf1        = DCount(LSParmsComp, @VM)
    Buf2        = Sum(LSParmsComp)
    If DCount(LSParmsComp, @VM) NE Sum(LSParmsComp) then 
		ErrorMsg  = 'Process Error: Recipe parameters for layer(s) '
		RDSLSKeys = Xlate('RDS', RDSNo, 'RDS_LS_SORTED', 'X')
		For each LSParmFlag in LSParmsComp using @VM setting vPos
			If Not(LSParmFlag) then
				LayerNo   = RDSLSKeys<0, vPos>
				LayerNo   = LayerNo[-1, 'B*']
				ErrorMsg := LayerNo : ','
			end
		Next LSParmsComp
		ErrorMsg[-1, 1]  = ''
        ErrorMsg        := ' have not been entered.'
        Error_Services('Set', ErrorMsg)
        Response         = False$
        return
    end
    
    Locate True$ in ParamOutOfSpec using @VM setting oPos then
		IF Supplement EQ False$ then
            ErrorMsg = 'Process Error: Recipe parameters are out of spec and no supplement has been set.'
            Error_Services('Set', ErrorMsg)
            Response = False$
            return
		end
    end
    IF TWAllSigned EQ False$ THEN
        ErrorMsg = 'Process Error: All Test Wafers have not been signed off.'
        Error_Services('Set', ErrorMsg)
        Response = False$
        return
    end

	//Added JRO 3/16/2021
    //Load Lock Required Check
    LoadLockReq = Xlate('REACTOR', Reactor, REACTOR_PICK_PLACE$, 'X');                      
    IF LoadLockReq AND LoadLockVal EQ '' then
        ErrorMsg = 'Process Error: Load Lock Side must be set to either Left or Right before signing.'
        Error_Services('Set', ErrorMsg)
        Response = False$
        return
    END

    //Check if Selected LoadLock is down - New JRO 3/16/21
    If LoadLockReq AND LoadLockVal NE '' then
        DisabledLL = Xlate('REACTOR', Reactor, REACTOR_ACTIVE_LL_DISABLED$, 'X')
        IF DisabledLL NE '' then
            ReactLLRec = Database_Services('ReadDataRow', 'REACT_LL', DisabledLL)
            SideDisabled = ReactLLRec<REACT_LL_DISABLED$>
            QualMode = ReactLLRec<REACT_LL_QUAL_MODE$>
            If LoadLockVal EQ SideDisabled AND (QualMode EQ '' OR QualMode EQ FALSE$) then
                ErrorMsg = 'Process Error: Selected Load Lock (':SideDisabled:') is currently down.'
                Error_Services('Set', ErrorMsg)
                Response = False$
                return
            end
            If QualMode then
                ReactLLRec<REACT_LL_QUAL_MODE$> = FALSE$
                Database_Services('WriteDataRow','REACT_LL',DisabledLL, ReactLLRec)
            end
        end
    end

    **********************
    * Verify ROTR Status *
    **********************
    ROTREnabled = Xlate('REACTOR', Reactor, 'ENABLE_ROTR', 'X')
    If (ReactorType = 'ASM+' OR ReactorType = 'HTR' OR ReactorType = 'ASM') AND (ROTREnabled EQ True$) then
                   
        If Reactor NE '' then
            ReactorRec = Database_Services('ReadDataRow', 'REACTOR', Reactor)
            ROTRStatus = ReactorRec<REACTOR_ROTR_STATUS$>    
            
            If ROTRStatus EQ 'F' then
                ErrorMsg = 'Process Error: Unable to load reactor due to ROTR load block. ' |
                             : 'A lead or supervisor must clear the ROTR load block.'
                Error_Services('Set', ErrorMsg)
                Response = False$
                return                                                                                       
            end
        end
    end
              
    If LoadSig NE '' then
        ErrorMsg = 'Process Error: Load stage has already been signed.'
        Error_Services('Set', ErrorMsg)
        Response = False$
        return
    end
       
    WOMatCurrStatus = obj_WO_Mat('CurrStatus',WONo:'*':CassNo)
    IF WOMatCurrStatus = 'HOLD' THEN
        ErrorMsg = 'Process Error: Cassette is on Hold and may not be signed off.'
        Error_Services('Set', ErrorMsg)
        Response = False$
        Return
    END

    FWIInstAckReq  = Xlate('RDS', RDSNo, 'FWI_INST_ACK_REQ', 'X')
    IF FWIInstAckReq EQ True$ THEN
        ErrorMsg = 'The FWI stage engineering instructions must be acknowledged before the load operation can be signed.'
        Error_Services('Set', ErrorMsg)
        Response = False$
        Return
    END
 
	If PreFlag EQ True$ then
		// PRE and LOAD stages are being signed together. This is usually because the operator is loading a reactor
		// using the barcode scanner, which attempts to sign both stages at once to save time.       
		Stage = 'VER'
	end else
		Stage = 'LOAD'
	end

	If ReactorType EQ 'EPP' then
		WOMatKey = ''
	end else
		WOMatKey = WONo:'*':CassNo
	end
	
	Signature_Services('CheckSigOrder', WOMatKey, Stage, False$, RDSNo)
	
	If Error_Services('HasError') then
		Response = False$
		Return
	end

    SigDate = RDSRec<RDS_PRE_EPI_SIG_DATE$>
    SigTime = RDSrec<RDS_PRE_EPI_SIG_TIME$>
    PreCINo = Xlate('RDS', RDSNo, 'PRE_CI_NO', 'X')

    LastCleanSigDTM = XLATE('CLEAN_INSP',PreCINo,CLEAN_INSP_CLEAN_VER_SIG_DTM$,'X')[-1,'B':@VM]

    IF LastCleanSigDTM NE '' THEN

        CurrDTM	= ICONV(OCONV(Date(),'D4/'):' ':OCONV(Time(),'MTS'),'DT')

        Delta = CurrDTM - LastCleanSigDTM

        IF Delta _GTX 1.0000000000 THEN
            ErrorMsg = 'Process Error: It has been more than 24 hours since the last cleaning... ' |
                     : 'Please re-clean the material and record in the pre epi window'
            Error_Services('Set', ErrorMsg)
            Response = False$
            return
        END
    END                      
  
    LoadInstAckReq  = Xlate('RDS', RDSNo, 'LOAD_INST_ACK_REQ', 'X')
    IF LoadInstAckReq EQ True$ THEN
        ErrorMsg = 'The LOAD stage engineering instructions must be acknowledged before the load operation can be signed.'
        Error_Services('Set', ErrorMsg)
        Response = False$
        Return
    END
    
    // Add check for supplement signatures
    UnacknowledgedSupp = Supplement_Services('UnacknowledgedSupplementCheck', 'RDS', RDSNo, 'LOAD')
    If UnacknowledgedSupp NE FALSE$ then
        ErrorMsg = 'The LOAD stage supplements must be acknowledged before the load operation can be signed.'
        Error_Services('Set', ErrorMsg)
        Response = False$
        return    
    end
    
    EndTick = GetTickCount()
    Mona_Services('QueueLatencyAndCountMetrics', MonaResource, MetricName, StartTick, EndTick)
    
    If ErrorMsg EQ '' then
        Response = True$
    end else
        Response = False$
        Error_Services('Add', ErrorMsg)
    end
    
end service

Service UnsignLoadSignatureReady(RDSNo)
    
    ErrorMessage = ''
    UnsignEligible = True$
    
    If RowExists('RDS', RDSNo) then
        RDSRec = Database_Services('ReadDataRow', 'RDS', RDSNo, True$, 0, False$)
        If Error_Services('NoError') then
            WONo = RDSRec<RDS_WO$>
            CassNo = RDSRec<RDS_CASS_NO$>
            WoMatKey = WONo : '*' : CassNo
            If RowExists('WO_MAT', WoMatKey) then
                WOMatCurrStatus = obj_WO_Mat('CurrStatus',WOMatKey)
                
                //Cases to check for various statuses that should block unsigning the unload.
                Begin Case
                    Case WOMatCurrStatus EQ 'HOLD'
                        ErrorMessage  = 'Cassette is on HOLD and may not have the signature removed.'
                    Case Otherwise$
                        
                End Case
                
                If ErrorMessage EQ '' then
                    //Check if any subsequent signature are set. If so RDS is not eligible.
                    UnloadExtra1Sig = RDSRec<RDS_OP_OUT_EX1$>
                    LoadExtra1Sig = RDSRec<RDS_OP_IN_EX2$>
                    UnloadExtra2Sig = RDSRec<RDS_OP_OUT_EX2$>
                    LoadExtra2Sig = RDSRec<RDS_OP_IN_EX3$>
                    UnloadSig   = RDSRec<RDS_OPERATOR_OUT$>Xlate('RDS', RDSNo, 'OPERATOR_OUT', 'X')
                    CISigs      = Xlate('RDS', RDSNo, 'LWI_CI_SIG', 'X')
                    swap @VM with '' in CISigs
                    ReactorType = XLATE('RDS', RDSNo, 'REACTOR_TYPE', 'X')
                    If ReactorType NE 'EPP' Then               
                        FQASig = Xlate('RDS', RDSNo, 'SUP_VER_SIG', 'X')                
                    end else
                        WMOutKey = Xlate('RDS', RDSNo, 'WM_OUT_KEY', 'X')
                        FQASig   = Xlate('WM_OUT', WMOutKey, 'SUP_VER_SIG', 'X')
                    end
                    
                    Begin Case
                        Case FQASig NE '' 
                            ErrorMessage = 'FQA Signature is set.'
                        Case CISigs NE ''
                            ErrorMessage = 'Clean and Inspect Signatures are set.'
                        Case UnloadSig NE ''
                            ErrorMessage = 'Unload signature is set.'
                        Case LoadExtra2Sig NE ''
                            ErrorMessage = 'Load extra 2 signature is set.'
                        Case UnloadExtra2Sig NE ''
                            ErrorMessage = 'Unload extra 2 signature is set.'
                        Case LoadExtra1Sig NE ''
                            ErrorMessage = 'Load extra 1 signature is set.'
                        Case UnloadExtra1Sig NE ''
                            ErrorMessage = 'Unload extra 1 signature is set.'
                    End Case
                    
                    
                end
            end else
                ErrorMessage = 'Unable to locate the WO_MAT record for this RDS in the WO_MAT table.' 
            end
        end else
            ErrorMessage = 'Error reading RDS Record. ' : Error_Services('GetMessage')
        end
        
    end else
       ErrorMessage = 'Unable to locate RDS in the RDS Table.' 
    end
    
    If ErrorMessage NE '' then
        UnsignEligible = False$
        ErrorMessage = 'Process Error: Unable to verify the load signature is Eligible to be unsigned: ' : ErrorMessage
        Error_Services('Add', ErrorMessage)
    end
    
    Response = UnsignEligible                 
    
end service


Service LoadExtra1stReady(RDSNo)
	
	ErrorMsg = ''
	Response = False$
	RDSRec                  = Database_Services('ReadDataRow', 'RDS', RDSNo)
	if Error_Services('NoError') then
	    StandardLoadSignature   = RDSRec<RDS_OPERATOR_IN$>
        UnloadExtra1Signature   = RDSRec<RDS_OP_OUT_EX1$>
        LoadExtra1Signature     = RDSRec<RDS_OP_IN_EX2$>
        UnloadExtra2Signature   = RDSRec<RDS_OP_OUT_EX2$>
        LoadExtra2Signature     = RDSRec<RDS_OP_IN_EX3$>
        StandardUnloadSignature = RDSRec<RDS_OPERATOR_OUT$>
        Reactor                 = RDSRec<RDS_REACTOR$>
        
        If Reactor NE '' then 
            WONo            = RDSRec<RDS_WO$>
            Query         = 'SELECT REACT_STATE WITH SCHED_WO CONTAINING ':WONo
            GoSub ClearCursors
            Set_Status(0)
            RList(Query, TARGET_ACTIVELIST$, '', '' '')
            SchedReactors = ''
            EOF           = False$
            Loop
                ReadNext SchedReactor else EOF = True$
            Until EOF
                SchedReactors<0, -1> = SchedReactor
            Repeat
            
            Locate Reactor in SchedReactors using @VM setting vPos then
                If StandardLoadSignature NE '' AND UnloadExtra1Signature NE '' AND LoadExtra1Signature EQ '' And LoadExtra2Signature EQ ''AND UnloadExtra2Signature EQ ''  and StandardUnloadSignature EQ '' then
                    Response = True$
                end else
                    ErrorMsg = 'RDS is not eligible to have Load Extra 1 signed.'
                end
            end else
                Swap @VM with ',' in SchedReactors
                ErrorMsg = 'Assigned reactor, ':Reactor:', does not equal any scheduled reactor(s), ':SchedReactors:'.'
            end
        end
	end else
	    ErrorMsg = Error_Services('GetMessage')
	end
	
	If ErrorMsg NE '' then
	    Error_Services('Add', ErrorMsg)
	end
	
end service


Service UnsignLoadExtra1stReady(RDSNo)
	
	ErrorMsg = ''
	Response = False$
	RDSRec                  = Database_Services('ReadDataRow', 'RDS', RDSNo)
	if Error_Services('NoError') then
	   StandardLoadSignature   = RDSRec<RDS_OPERATOR_IN$>
        UnloadExtra1Signature   = RDSRec<RDS_OP_OUT_EX1$>
        LoadExtra1Signature     = RDSRec<RDS_OP_IN_EX2$>
        UnloadExtra2Signature   = RDSRec<RDS_OP_OUT_EX2$>
        LoadExtra2Signature     = RDSRec<RDS_OP_IN_EX3$>
        StandardUnloadSignature = RDSRec<RDS_OPERATOR_OUT$>
        If LoadExtra1Signature NE '' then
            If UnloadExtra2Signature EQ '' AND LoadExtra2Signature EQ '' and StandardUnloadSignature EQ '' then
                Response = True$
            end
        end 
	end else
	    ErrorMsg = Error_Services('GetMessage')
	end
	
	If ErrorMsg NE '' then
	    Error_Services('Add', ErrorMsg)
	end
	
end service


Service LoadExtra2ndReady(RDSNo)
	
	ErrorMsg = ''
	Response = False$
	RDSRec                  = Database_Services('ReadDataRow', 'RDS', RDSNo)
	If Error_Services('NoError') then
	    StandardLoadSignature   = RDSRec<RDS_OPERATOR_IN$>
        UnloadExtra1Signature   = RDSRec<RDS_OP_OUT_EX1$>
        LoadExtra1Signature     = RDSRec<RDS_OP_IN_EX2$>
        UnloadExtra2Signature   = RDSRec<RDS_OP_OUT_EX2$>
        LoadExtra2Signature     = RDSRec<RDS_OP_IN_EX3$>
        StandardUnloadSignature = RDSRec<RDS_OPERATOR_OUT$>
        Reactor                 = RDSRec<RDS_REACTOR$>
        
        If Reactor NE '' then 
            WONo            = RDSRec<RDS_WO$>
            Query         = 'SELECT REACT_STATE WITH SCHED_WO CONTAINING ':WONo
            ClearSelect
            Set_Status(0)
            RList(Query, TARGET_ACTIVELIST$, '', '' '')
            SchedReactors = ''
            EOF           = False$
            Loop
                ReadNext SchedReactor else EOF = True$
            Until EOF
                SchedReactors<0, -1> = SchedReactor
            Repeat
            
            Locate Reactor in SchedReactors using @VM setting vPos then
                If StandardLoadSignature NE '' AND UnloadExtra1Signature NE '' AND LoadExtra1Signature NE '' AND UnloadExtra2Signature NE '' And LoadExtra2Signature EQ '' and StandardUnloadSignature EQ '' then
                    Response = True$
                end else
                    ErrorMsg = 'RDS is not eligible to have the Load Extra 2 set.'
                end
            end else
                Swap @VM with ',' in SchedReactors
                ErrorMsg = 'Assigned reactor, ':Reactor:', does not equal any scheduled reactor(s), ':SchedReactors:'.'
                Response     = False$
            end
        end
	end else
	    ErrorMsg = Error_Services('GetMessage')
	end
	
	If ErrorMsg NE '' then
	    Error_Services('Add', ErrorMsg)
	end

end service


Service UnsignLoadExtra2ndReady(RDSNo)
	
	ErrorMsg = ''
	Response = False$
	RDSRec                  = Database_Services('ReadDataRow', 'RDS', RDSNo)
	If Error_Services('NoError') then
        StandardLoadSignature   = RDSRec<RDS_OPERATOR_IN$>
        UnloadExtra1Signature   = RDSRec<RDS_OP_OUT_EX1$>
        LoadExtra1Signature     = RDSRec<RDS_OP_IN_EX2$>
        UnloadExtra2Signature   = RDSRec<RDS_OP_OUT_EX2$>
        LoadExtra2Signature     = RDSRec<RDS_OP_IN_EX3$>
        StandardUnloadSignature = RDSRec<RDS_OPERATOR_OUT$>
        If LoadExtra2Signature NE '' then
            If StandardUnloadSignature EQ '' then
                Response = True$
            end		
        end
	end else
	    ErrorMsg = Error_Services('GetMessage')
	end
	
	If ErrorMsg NE '' then
	    Error_Services('Add', ErrorMsg)
	end
	
end service


//----------------------------------------------------------------------------------------------------------------------
// SignLoadStage
//
// Input:
//  RDSNo     - RDS to be signed
//  Username  - LSL Username of the operator signing the stage.
//  WaferQty  - User supplied wafer quantity to verify against the schedule.
//
// Output:
//  Response  - True$ if LOAD stage is ready to be signed (all preconditions met and no errors encountered),
//              False$ otherwise.
//----------------------------------------------------------------------------------------------------------------------
Service SignLoadStage(RDSNo, Username, WaferQty, LLSide, ScanEntry)
    StartTick = GetTickCount()
    MetricName = 'SignLoadStage'
    
    Error_Services('Clear')
    If (RDSNo EQ '') or (Username EQ '') or (WaferQty EQ '') then
        ErrorMessage = 'RDSNo, Username, or WaferQty not supplied in the ':Service:' service.'
        Error_Services('Set', ErrorMessage)
        Response = False$
        return
    end
    
    RDSRec = Database_Services('ReadDataRow', 'RDS', RDSNo)
    If Error_Services('HasError') then
        ErrorMsg = 'Error reading RDS record: ':RDSNo:' in the ':Service:' service.'
        Error_Services('Set', ErrorMsg)
        Response = False$
        return
    end
    
    RDSRec<RDS_WAFERS_IN$>      = WaferQty
    RDSRec<RDS_LOAD_LOCK_SIDE$> = LLSide
    Database_Services('WriteDataRow', 'RDS', RDSNo, RDSRec, True$, False$, True$)
    
    WONo            = RDSRec<RDS_WO$>
    CassNo          = RDSRec<RDS_CASS_NO$>
    Reactor         = RDSRec<RDS_REACTOR$>
    PSN             = RDSRec<RDS_PROD_SPEC_ID$>
    WOStep          = Xlate('RDS', RDSNo, 'WO_STEP', 'X')
    ReactorType     = Xlate('REACTOR', Reactor, REACTOR_REACT_TYPE$, 'X')
    LoadLockVal     = LLSide
    
    TWAllSigned     = Xlate('RDS', RDSNo, RDS_ALL_TW_USE_SIGNED$, 'X')
    If TWAllSigned EQ '' then
    	TWAllSigned = True$
    end
    
    RunOrderNo      = Xlate('RDS', RDSNo, 'RUN_ORDER_NUM', 'X')        
    SchedQty        = RDSRec<RDS_CASS_WAFER_QTY$>

    CurrReactorLoadCapacity = Reactor_Services('GetReactorAvailChamberCount', Reactor)
    If CurrReactorLoadCapacity LE 0 then
    	ErrorMsg = 'Error: Reactor load limit has been reached.'
        Error_Services('Set', ErrorMsg)
        Response = False$
        return
    end
           
    SigDt = OCONV( Date(), 'D2/' )
    SigTm = OCONV( Time(), 'MTS' )                                                                         
    LoadDTM 	= SigDt:' ':SigTm
    
    IF ReactorType NE 'EPP' THEN

        ReactID		= 'R':Reactor
        ReactWH		= XLATE('REACTOR',Reactor,'TOOL_WH','X')
        ReactLoc	= XLATE('REACTOR',Reactor,'TOOL_LOC','X')				   							   							   		
        Tags		= RDSNo

        owmParms = WONo:@RM:CassNo:@RM:WOStep:@RM:'LOAD':@RM:Username:@RM:LoadDTM:@RM:ReactID:@RM:ReactWH:@RM:ReactLoc:@RM:Tags	
                                    
        IF Get_Status(errCode) THEN
            ErrorMsg = 'Process Error: Error calling obj_WO_Mat("SetSignature"). Error code: ':errCode
            Error_Services('Set', ErrorMsg)
            Response = False$
            Return
        END			
        
        LogFile	= 'WO_MAT'			; WOMLParms  = LogFile:@RM
        LogDTM	= LoadDTM			; WOMLParms := LogDTM:@RM
        Action	= WOStep:'LOAD'		; WOMLParms := Action:@RM
        WhCd	= ReactWH			; WOMLParms := WhCd:@RM
        LocCd	= ReactLoc			; WOMLParms := LocCd:@RM
        WONos	= WONo				; WOMLParms := WONos:@RM
        CassNos	= CassNo			; WOMLParms := CassNos:@RM
        UserID	= Username			; WOMLParms := UserID:@RM
        Tags	= RDSNo				; WOMLParms := Tags:@RM
        ToolID	= ReactID			; WOMLParms := ToolID:@RM
        WOMLParms := ScanEntry
        
        Service_Services('PostProcedure', 'obj_WO_Mat_Log', 'Create':SD$:WOMLParms)
                                    
        IF Get_Status(errCode) THEN
            ErrorMsg = 'Process Error: Error calling Service_Services("PostProcedure", "obj_WO_Mat_Log", "Create"...). Error code: ':errCode
            Error_Services('Set', ErrorMsg)
            LogData = ''
            LogData<1> = LoggingDTM
            LogData<2> = @User4
            LogData<3> = RDSNo
            LogData<4> = 'Error calling Service_Services("PostProcedure", "obj_WO_Mat_Log", "Create"...) within the ':Service:' service. Error code: ':errCode
            Logging_Services('AppendLog', objFSErrorLog, LogData, @RM, @FM)
            Response = False$
            Return
        END                                                           
                                   
        IdleTime = obj_React_Status('CassLoad',Reactor:@RM:WONo:@RM:RunOrderNo:@RM:LoadDTM:@RM:RDSNo)	
                                
        IF Get_Status(errCode) THEN
            LogData = ''
            LogData<1> = LoggingDTM
            LogData<2> = @User4
            LogData<3> = RDSNo
            LogData<4> = 'Error calling obj_React_Status("CassLoad") within the ':Service:' service. Error code: ':errCode
            Logging_Services('AppendLog', objFSErrorLog, LogData, @RM, @FM)
        END ELSE
            RDSRec<RDS_REACT_IDLE_TIME$> = IdleTime
        END
                                           
    END ELSE
                                                      
        * Update REACT_STATUS record                                       
        AllInCassettes = RDSRec<RDS_IN_CASS_NO$>
        LOOP
            LastChar = AllInCassettes[-1,1]
        UNTIL LastChar NE @VM OR AllInCassettes = ''
            AllInCassettes[-1,1] = ''
        REPEAT
        
        InCassettes = ''
        CassCnt = COUNT(AllInCassettes,@VM) + (AllInCassettes NE '')
                                    
        FOR N = 1 TO CassCnt
            InCass = AllInCassettes<1,N>
            LOCATE InCass IN InCassettes USING @VM SETTING Pos ELSE
                InCassettes = INSERT(InCassettes,1,Pos,0,InCass)
            END
        NEXT N
                                    
        IdleTime = obj_React_Status('ReactorLoad',Reactor:@RM:WONo:@RM:InCassettes:@RM:LoadDTM:@RM:RDSNo)

        IF Get_Status(errCode) THEN
            LogData = ''
            LogData<1> = LoggingDTM
            LogData<2> = @User4
            LogData<3> = RDSNo
            LogData<4> = 'Error calling obj_React_Status("ReactorLoad") within the ':Service:' service. Error code: ':errCode
            Logging_Services('AppendLog', objFSErrorLog, LogData, @RM, @FM)
        END ELSE
            RDSRec<RDS_REACT_IDLE_TIME$> = IdleTime
        END	;* Added Else branch 3/22/2012 JCH
                                                                
    END		;* End of check for valid load signature

    RDSRec<RDS_OPERATOR_IN$> = Username
    RDSRec<RDS_DATE_IN$>     = IConv(SigDt, 'D')
    RDSRec<RDS_TIME_IN$>     = IConv(SigTm, 'MT')

    // Set LOAD signature in RDS record
    Database_Services('WriteDataRow', 'RDS', RDSNo, RDSRec, True$, False$, True$)
    
    If Error_Services('NoError') then
        If RDS_Services('IsEpiPro', RDSNo) then
            LotId = Lot_Services('GetLotIdByLegacyLotIdAndType', RDSNo, 'RDS')
            If Error_Services('NoError') then
                Lot_Services('BeginRun', LotId, Username, Reactor) ; // Begin run at REACTOR_RUN operation
            end
        end
    end
    
    If Error_Services('NoError') then
		Rds_Services('AddComment', RDSNo, 'Load Stage signed onto Reactor ' : Reactor, Username)
        If ReactorType EQ 'EPP' then
            Reactor_Services('IncrementWfrMetrics', RDSNo)
        end
        Response = True$
    end else
        Response = False$
    end
    
    EndTick = GetTickCount()
    Mona_Services('QueueLatencyAndCountMetrics', MonaResource, MetricName, StartTick, EndTick)
end service


//----------------------------------------------------------------------------------------------------------------------
// UnloadSignatureReady
//
// Input:
//  RDSNo     - RDS to be signed
//  Username  - LSL Username of the operator signing the stage.
//
// Output:
//  Response  - True$ if UNLOAD stage is ready to be signed (all preconditions met and no errors encountered),
//              False$ otherwise.
//----------------------------------------------------------------------------------------------------------------------
Service UnloadSignatureReady(RDSNo, Username, Reactor)
	StartTick = GetTickCount()
    MetricName = 'UnloadSignatureReady'

    If (RDSNo EQ '') or (Username EQ '') or (Reactor EQ '') then
        ErrorMsg = 'RDSNo or Username or Reactor not supplied in the ':Service:' service.'
        Error_Services('Set', ErrorMsg)
        Response = False$
        Return
    end
    
    RDSRec = Database_Services('ReadDataRow', 'RDS', RDSNo)
    If Error_Services('HasError') then
        ErrorMsg = 'Error reading RDS record: ':RDSNo:' in the ':Service:' service.'
        Error_Services('Add', ErrorMsg)
        Response = False$
        return
    end
    
    WONo       = RDSRec<RDS_WO$>
    CassNo     = RDSRec<RDS_CASS_NO$>
    WOStep     = Xlate('RDS', RDSNo, 'WO_STEP', 'X')
    RDSSig     = RDSRec<RDS_OPERATOR_OUT$>
    RDSReactor = RDSRec<RDS_REACTOR$>
    
    If RDSReactor NE Reactor then
        ErrorMsg = 'Process Error: Unload reactor does not match the load reactor.'
        Error_Services('Set', ErrorMsg)
        Response = False$
        return
    end

    WOMatCurrStatus = obj_WO_Mat('CurrStatus',WONo:'*':CassNo)

    IF WOMatCurrStatus = 'HOLD' THEN
        ErrorMsg  = 'Process Error: Cassette is on Hold and may not be signed off.'
        Error_Services('Set', ErrorMsg)
        Response = False$
        return
    END
	ParamOutOfSpec  = Xlate('RDS', RDSNo, 'PARAM_OUT_OF_SPEC', 'X')
    Supplement   = Supplement_Services('HasSupplements', 'RDS', RDSNo, '')
    Locate True$ in ParamOutOfSpec using @VM setting oPos then
        IF Supplement EQ False$ then
            ErrorMsg = 'Process Error: Recipe parameters are out of spec and no supplement has been set.'
            Error_Services('Set', ErrorMsg)
            Response = False$
            return
        end
    end
    ReactorType = XLATE('RDS',RDSNo,'REACTOR_TYPE','X')

	If ReactorType EQ 'EPP' then
		WOMatKey = ''
	end else
		WOMatKey = WONo:'*':CassNo
	end
	
	Signature_Services('CheckSigOrder', WOMatKey, 'UNLOAD', RDSNo)
	If Error_Services('HasError') then
		Response = False$
		return
	end
    
    LWIInstAckReq = Xlate('RDS', RDSNo, 'LWI_INST_ACK_REQ', 'X')
    If LWIInstAckReq EQ True$ then
        ErrorMsg = 'The LWI stage engineering instructions must be acknowledged before the load operation can be signed.'
        Error_Services('Set', ErrorMsg)
        Response = False$
        return
    end
    
    UnloadInstAckReq = Xlate('RDS', RDSNo, 'UNLOAD_INST_ACK_REQ', 'X')
    If UnloadInstAckReq EQ True$ then
        ErrorMsg = 'The UNLOAD stage engineering instructions must be acknowledged before the load operation can be signed.'
        Error_Services('Set', ErrorMsg)
        Response = False$
        return
    end
                  
    * Check for extra unloads without corresponding extra load                              
    IF RDSRec<RDS_OP_OUT_EX1_DATE$> <> '' THEN
            IF RDSRec<RDS_OP_IN_EX2_DATE$> = '' THEN
                ErrorMsg = 'Process Error: You have an extra unload defined in the first wafer area, however, '     |
                         : 'it must be reloaded there as well...You have the provision of overriding '              |
                         : 'the date and time in, due to it being forgotten.'
                Error_Services('Add', ErrorMsg)
                Response = False$
                return
            END
        END

    IF RDSRec<RDS_OP_OUT_EX2_DATE$> <> '' THEN
        IF RDSRec<RDS_OP_IN_EX3_DATE$> = '' THEN
            ErrorMsg = 'Process Error You have a second extra unload defined in the first wafer area, however, '    |
                     : 'it must be reloaded there as well...You have the provision of overriding '                  |
                     : 'the date and time in, due to it being forgotten.'
            Error_Services('Add', ErrorMsg)
            Response = False$
            return
        END
    END
    
    // Verify runtime is not greater than 2 days.
    // Calculate runtime
    LoadDTM      = IConv(OConv(RDSRec<RDS_DATE_IN$>, 'D'):' ':OConv(RDSRec<RDS_TIME_IN$>, 'MT'), 'DT')
    LoadEx2DTM   = IConv(OConv(RDSRec<RDS_OP_IN_EX2_DATE$>, 'D'):' ':OConv(RDSRec<RDS_OP_IN_EX2_TIME$>, 'MT'), 'DT')
    LoadEx3DTM   = IConv(OConv(RDSRec<RDS_OP_IN_EX3_DATE$>, 'D'):' ':OConv(RDSRec<RDS_OP_IN_EX3_TIME$>, 'MT'), 'DT')
    UnloadEx1DTM = IConv(OConv(RDSRec<RDS_OP_OUT_EX1_DATE$>, 'D'):' ':OConv(RDSRec<RDS_OP_OUT_EX1_TIME$>, 'MT'), 'DT')
    UnloadEx2DTM = IConv(OConv(RDSRec<RDS_OP_OUT_EX2_DATE$>, 'D'):' ':OConv(RDSRec<RDS_OP_OUT_EX2_TIME$>, 'MT'), 'DT')
    UnloadDTM    = Datetime()
    Begin Case
        Case UnloadEx2DTM NE ''
            Runtime = UnloadEx2DTM - LoadEx3DTM
        Case UnloadEx1DTM NE ''
            Runtime = UnloadEx1DTM - LoadEx2DTM
        Case Otherwise$
            Runtime = UnloadDTM - LoadDTM
    End Case

    Supervisor = Memberof(@User4, 'SUPERVISOR')
    Lead       = Memberof(@User4, 'LEAD')
    
    If ( (Runtime GT 3) and Not(Lead or Supervisor) ) then
        ErrorMsg = 'Cassette runtime exceeds three days! An override by a LEAD or SUPERVISOR is required.'
        Error_Services('Add', ErrorMsg)
        Response = False$
        return
    end
    
    If ( (Runtime GT 3) and (Lead or Supervisor) ) then
        Override_Services('SetOverride', 'RDS', RDSNo, 'UNLOAD_DTM', @User4)
    end
    
    // Add check for supplement signatures
    UnacknowledgedSupp = Supplement_Services('UnacknowledgedSupplementCheck', 'RDS', RDSNo, 'UNLOAD')
    If UnacknowledgedSupp NE FALSE$ then
        ErrorMessage = 'The UNLOAD stage supplements must be acknowledged before the unload operation can be signed.'
        Error_Services('Set', ErrorMessage)
        Response = False$
        return    
    end
    
    If Error_Services('NoError') then
        Response = True$
    end else
        Response = False$
    end
    
    EndTick = GetTickCount()
    Mona_Services('QueueLatencyAndCountMetrics', MonaResource, MetricName, StartTick, EndTick)
end service


Service UnloadExtra1stReady(RDSNo)
	
	Response = False$
	RDSRec                  = Database_Services('ReadDataRow', 'RDS', RDSNo)
	StandardLoadSignature   = RDSRec<RDS_OPERATOR_IN$>
	UnloadExtra1Signature   = RDSRec<RDS_OP_OUT_EX1$>
	LoadExtra1Signature     = RDSRec<RDS_OP_IN_EX2$>
	UnloadExtra2Signature   = RDSRec<RDS_OP_OUT_EX2$>
	LoadExtra2Signature     = RDSRec<RDS_OP_IN_EX3$>
	StandardUnloadSignature = RDSRec<RDS_OPERATOR_OUT$>
	If StandardLoadSignature NE '' AND UnloadExtra1Signature EQ '' AND LoadExtra1Signature EQ '' AND UnloadExtra2Signature EQ '' And LoadExtra2Signature eq '' and StandardUnloadSignature EQ '' then
		Response = True$
	end
	
end service


Service UnloadExtra2ndReady(RDSNo)
	
	Response = False$
	RDSRec                  = Database_Services('ReadDataRow', 'RDS', RDSNo)
	StandardLoadSignature   = RDSRec<RDS_OPERATOR_IN$>
	UnloadExtra1Signature   = RDSRec<RDS_OP_OUT_EX1$>
	LoadExtra1Signature     = RDSRec<RDS_OP_IN_EX2$>
	UnloadExtra2Signature   = RDSRec<RDS_OP_OUT_EX2$>
	LoadExtra2Signature     = RDSRec<RDS_OP_IN_EX3$>
	StandardUnloadSignature = RDSRec<RDS_OPERATOR_OUT$>
	If StandardLoadSignature NE '' AND UnloadExtra1Signature NE '' AND LoadExtra1Signature NE '' And LoadExtra2Signature EQ ''AND UnloadExtra2Signature EQ ''  and StandardUnloadSignature EQ '' then
		Response = True$
	end
	
end service


Service UnsignUnloadExtra1stReady(RDSNo)
	
	Response = False$
	RDSRec                  = Database_Services('ReadDataRow', 'RDS', RDSNo)
	StandardLoadSignature   = RDSRec<RDS_OPERATOR_IN$>
	UnloadExtra1Signature   = RDSRec<RDS_OP_OUT_EX1$>
	LoadExtra1Signature     = RDSRec<RDS_OP_IN_EX2$>
	UnloadExtra2Signature   = RDSRec<RDS_OP_OUT_EX2$>
	LoadExtra2Signature     = RDSRec<RDS_OP_IN_EX3$>
	StandardUnloadSignature = RDSRec<RDS_OPERATOR_OUT$>
	If UnloadExtra1Signature NE '' then
		If LoadExtra1Signature EQ '' AND UnloadExtra2Signature EQ '' AND LoadExtra2Signature EQ '' and StandardUnloadSignature EQ '' then
			Response = True$
		end
	end

end service


Service UnsignUnloadExtra2ndReady(RDSNo)
	
	Response = False$
	RDSRec                  = Database_Services('ReadDataRow', 'RDS', RDSNo)
	StandardLoadSignature   = RDSRec<RDS_OPERATOR_IN$>
	UnloadExtra1Signature   = RDSRec<RDS_OP_OUT_EX1$>
	LoadExtra1Signature     = RDSRec<RDS_OP_IN_EX2$>
	UnloadExtra2Signature   = RDSRec<RDS_OP_OUT_EX2$>
	LoadExtra2Signature     = RDSRec<RDS_OP_IN_EX3$>
	StandardUnloadSignature = RDSRec<RDS_OPERATOR_OUT$>
	If UnloadExtra2Signature NE '' then
		If LoadExtra2Signature EQ '' and StandardUnloadSignature EQ '' then
			Response = True$
		end		
	end
	
end service


//----------------------------------------------------------------------------------------------------------------------
// SignUnloadStage
//
// Input:
//  RDSNo     - RDS to be signed
//  Username  - LSL Username of the operator signing the stage.
//
// Output:
//  Response  - True$ if UNLOAD stage was successfully signed , False$ otherwise.
//----------------------------------------------------------------------------------------------------------------------
Service SignUnloadStage(RDSNo, Username, ScanEntry)
    StartTick = GetTickCount()
    MetricName = 'SignUnloadStage'
    
    If (RDSNo EQ '') or (Username EQ '') then
        ErrorMsg = 'RDSNo or Username not supplied in the ':Service:' service.'
        Error_Services('Add', ErrorMsg)
        Response = False$
        Return
    end
    
    RDSRec = Database_Services('ReadDataRow', 'RDS', RDSNo)
    If Error_Services('HasError') then
        ErrorMsg = 'Error reading RDS record: ':RDSNo:' in service: ':Service
        Error_Services('Add', ErrorMsg)
        Response = False$
        return
    end

    WONo      = RDSRec<RDS_WO$>
    CassNo    = RDSRec<RDS_CASS_NO$>
    WOStep    = Xlate('RDS', RDSNo, 'WO_STEP', 'X')
    RDSSig    = RDSRec<RDS_OPERATOR_OUT$>
    ReactType = Xlate('RDS', RDSNo, 'REACTOR_TYPE', 'X')
    IF RDSSig  = '' THEN

        WOMatRec = XLATE('WO_MAT',WONo:'*':CassNo,'','X')
        
        LOCATE WOStep:'UNLOAD' IN WOMatRec<WO_MAT_SIG_PROFILE$> USING @VM SETTING Pos THEN
        
            CurrSig = WOMatRec<WO_MAT_SIGNATURE$,Pos>
            
            IF CurrSig NE '' THEN
            
                * Already signed off without saving the RDS signatures
                
                CurrSigDTM  = WOMatRec<WO_MAT_SIG_DTM$,Pos>
                CurrSigDate = CurrSigDTM[1, 'F.']
                CurrSigTime = CurrSigDTM[-1, 'B.']
                               
                RDSRec<RDS_OPERATOR_OUT$>  = CurrSig
                RDSRec<RDS_DATE_OUT$>      = CurrSigDate
                RDSRec<RDS_TIME_OUT$>      = CurrSigTime
                
                /* Sync up the RDS record with WO_MAT */
                Database_Services('WriteDataRow', 'RDS', RDSNo, RDSRec, True$, False$, False$)
                If ReactType NE 'EPP' then
                   Reactor_Services('IncrementWfrMetrics', RDSNo) 
                end
                Response = True$        
                RETURN               
            END
        END
    END
  
    SigDt = OCONV( date(), 'D2/' )
    SigTm = OCONV( time(), 'MTS' )
    SigBy = Username
                    
    WONo		= RDSRec<RDS_WO$>
    CassNo		= RDSRec<RDS_CASS_NO$>
    WOStep		= Xlate('RDS', RDSNo, 'WO_STEP', 'X')
    ReactorType	= Xlate('RDS', RDSNo, 'REACTOR_TYPE', 'X')
    
    UnloadDTM = SigDt:' ':SigTm
    Reactor   = RDSRec<RDS_REACTOR$>
    
    IF ReactorType NE 'EPP' THEN
        
        ReactID		= 'R':Reactor	
        ReactWH		= XLATE('REACTOR',Reactor,'TOOL_WH','X')
        ReactLoc	= XLATE('REACTOR',Reactor,'TOOL_LOC','X')
        Tag			= RDSNo

        * 4/30/2013 JCH added parms for merging of two methods			
        owmParms = WONo:@RM:CassNo:@RM:WOStep:@RM:'UNLOAD':@RM:Username:@RM:SigDt:' ':SigTm:@RM:ReactID:@RM:ReactWH:@RM:ReactLoc:@RM:Tag		
                        
        IF Get_Status(errCode) THEN
            ErrorMsg = 'Process Error: Error calling Obj_WO_Mat("SetSignature") within the ':Service:' service'
            Error_Services('Add', ErrorMsg)
            Response = False$
            return
        END
                    
        IF WONo NE '' AND CassNo NE '' THEN
                                        
            LogFile	= 'WO_MAT'			; WOMLParms  = LogFile:@RM
            LogDTM	= UnloadDTM			; WOMLParms := LogDTM:@RM
            Action	= WOStep:'UNLOAD'	; WOMLParms := Action:@RM
            WhCd	= ReactWH			; WOMLParms := WhCd:@RM
            LocCd	= ReactLoc			; WOMLParms := LocCd:@RM
            WONos	= WONo				; WOMLParms := WONos:@RM
            CassNos	= CassNo			; WOMLParms := CassNos:@RM
            UserID	= Username			; WOMLParms := UserID:@RM
            Tags	= RDSNo				; WOMLParms := Tags:@RM
            ToolID	= ReactID			; WOMLParms := ToolID:@RM
            WOMLParms := ScanEntry

            obj_WO_Mat_Log('Create',WOMLParms)			;* Add to WO_MAT Event Log
            
            IF Get_Status(errCode) THEN
                ErrorMsg = 'Process Error: Error calling Obj_WO_Mat_Log("Create") within the ':Service:' service'
                Error_Services('Add', ErrorMsg)
                Response = False$
                return
            END

            obj_React_Status('CassUnload',Reactor:@RM:WONo:@RM:CassNo:@RM:UnloadDTM:@RM:RDSNo)
            IF Get_Status(errCode) THEN
                LogData = ''
                LogData<1> = LoggingDTM
                LogData<2> = @User4
                LogData<3> = RDSNo
                LogData<4> = 'Error calling Obj_React_Status("CassUnload") within the ':Service:' service. Error code: ':errCode
                Logging_Services('AppendLog', objFSErrorLog, LogData, @RM, @FM)
            end
            Reactor_Services('IncrementWfrMetrics', RDSNo)
        END	;* End of check for WONo and CassNo 
        ;* End of check for EpiPRO reactor type            
    END else
        * EpiPro
        InCassettes = RDSRec<RDS_IN_CASS_NO$>
        InCassettes = SRP_Array('Clean', InCassettes, 'TrimAndMakeUnique', @VM)
        obj_React_Status('ReactorUnload',Reactor:@RM:WONo:@RM:InCassettes:@RM:UnloadDTM:@RM:RDSNo)
    end
                    
    RDSRec<RDS_OPERATOR_OUT$> = SigBy
    RDSRec<RDS_DATE_OUT$>     = IConv(SigDt, 'D')
    RDSRec<RDS_TIME_OUT$>     = IConv(SigTm, 'MT')

    /* Sync up the RDS record with WO_MAT */
    Database_Services('WriteDataRow', 'RDS', RDSNo, RDSRec, True$, False$, True$)
    If Error_Services('NoError') then
        CurReactor = RDSRec<RDS_REACTOR$>
        
        * * Set Current Reactor Information on the RDS record **
        * * Flag Graphite records if this PSN was using 1% Boron dopant * *
        
        PSNo            = RDSRec<RDS_PROD_SPEC_ID$>
        ReactRec		= XLATE('REACTOR', CurReactor, '', 'X')
        CurrInjSetting	= XLATE('REACT_STATE', CurReactor, 'CURR_INJ_SETTINGS', 'X')
        Dopants			= XLATE('PROD_SPEC', PSNo, 'LS_DOPANT', 'X')
        
        IF INDEX(Dopants,'1% Boron',1) THEN
            GraphiteRINos = obj_Reactor('CurrGraphite',CurReactor)
            
            grCnt = COUNT(GraphiteRINos,@VM) + (GraphiteRINos NE '')
            
            FOR I = 1 TO grCnt
                GraphiteRINo	= GraphiteRINos<1,I>
                PPlusFlag		= XLATE('REACT_ITEM', GraphiteRINo, REACT_ITEM_PPLUS_RDS_NO$, 'X')
                IF PPLusFlag = '' THEN
                    oplParms  = 'REACT_ITEM':@RM
                    oplParms := GraphiteRINo:@RM
                    oplParms := REACT_ITEM_PPLUS_RDS_NO$:@RM
                    oplParms := RDSNo
                    
                    obj_Post_Log('Create',oplParms)	;* Add RDSNo to Tube Record indicating the tube has been used with PPlus Dopant
                    
                END	;* End of check for existing RdsNo on the Graphite Record
            NEXT I
        END	;* End of check for '1% Boron' in the Dopant list
        
        TubeID			= obj_Reactor('CurrItem',CurReactor:@RM:'T':@RM:ReactRec)
        TubeGrade		= XLATE('REACT_ITEM',TubeID,REACT_ITEM_TUBE_GRADE$,'X')
        SusceptorID		= obj_Reactor('CurrItem',CurReactor:@RM:'S':@RM:ReactRec)
        
        RDSRec<RDS_INJECTORS$>     = CurrInjSetting
        RDSRec<RDS_TUBE_ID$>       = TubeID
        RDSRec<RDS_TUBE_GRADE$>    = TubeGrade
        RDSRec<RDS_SUSCEPTOR_ID$>  = SusceptorID
        
        Database_Services('WriteDataRow', 'RDS', RDSNo, RDSRec, True$, False$, True$)
        If Error_Services('NoError') then
              
            * Sets Reactor State with parameters from the PROD_SPEC record
            obj_React_State('ReactRunUnload',CurReactor:@RM:PSNo:@RM:RDSNo:@RM:UnloadDTM)	
            
            IF Get_Status(errCode) THEN
                LogData = ''
                LogData<1> = LoggingDTM
                LogData<2> = @User4
                LogData<3> = RDSNo
                LogData<4> = 'Error calling Obj_React_State("ReactRunUnload") within the ':Service:' service. Error code: ':errCode
                Logging_Services('AppendLog', objFSErrorLog, LogData, @RM, @FM)
            end
        end
    end
    
    If Error_Services('NoError') then
        If RDS_Services('IsEpiPro', RDSNo) then
            LotId = Lot_Services('GetLotIdByLegacyLotIdAndType', RDSNo, 'RDS')
            If Error_Services('NoError') then
                Lot_Services('EndRun', LotId, Username, Reactor) ; // End run at REACTOR_RUN operation
                Lot_Services('MoveOutLot', LotId, Username)      ; // Move out of REACTOR_RUN operation
                Lot_Services('MoveInLot', LotId, Username)       ; // Move into POST_EPI operation
                // If no production wafers in this run, then move out of POST_EPI and close the lot.
                If Rds_Services('IsEpiProTestRun', RDSNo) then
                    Lot_Services('AutoCloseLot', LotId, 'SYSTEM')
                end
            end
        end
    end
    
    If Error_Services('NoError') then
    	Rds_Services('AddComment', RDSNo, 'Unload Stage signed onto Reactor ' : Reactor, Username)
        Response = True$
    end else
        Response = False$
    end
    
    EndTick = GetTickCount()
    Mona_Services('QueueLatencyAndCountMetrics', MonaResource, MetricName, StartTick, EndTick)
end service


Service PostEpiSignatureReady(RDSNo, User)
    
    Response = True$
    ErrorMsg = ''
    
    If ( (RDSNo EQ '') or (User EQ '') ) then
        ErrorMsg = 'Error in ':Service:' service. Null RDSNo or User passed in.'
        Error_Services('Add', ErrorMsg)
        Response = False$
        return
    end
        
    RDSRec         = Database_Services('ReadDataRow', 'RDS', RDSNo)
    If Error_Services('HasError') then
        Response = False$
        return
    end
    
    WONo            = RDSRec<RDS_WO$>
    CassNo          = RDSRec<RDS_CASS_NO$>
    Reactor         = RDSRec<RDS_REACTOR$>
    WOStep          = 1
    WOMatCurrStatus = obj_WO_Mat('CurrStatus',WONo:'*':CassNo)

    IF WOMatCurrStatus = 'HOLD' THEN
        ErrorMsg = 'Error in ':Service:' service. Cassette is on Hold and may not be signed off.'
        Error_Services('Add', ErrorMsg)
        Response = False$
        return 
    END

    Set_Status(0)
    obj_WO_Mat('MQAComp',WONo:'*':CassNo:@RM:WOStep:@RM:'UNLOAD')

    IF Get_Status(errCode) THEN
        ErrorMsg = 'Error in ':Service:' service. ':errCode
        Error_Services('Add', ErrorMsg)
        RETURN 0
    END
    
    // Ensure all previous stages are complete
    ReactorType = XLATE('RDS',RDSNo,'REACTOR_TYPE','X')

    IF ReactorType NE 'EPP' THEN
        WOMatKey = WONo:'*':CassNo
        Signature_Services('CheckSigOrder', WOMatKey, 'POST')
        If Error_Services('HasError') then
            Response = False$
            return
        end
    end

    * Added 1/28/2013 JCH *
    // Ensure any POST stage specific instructions have been acknowledged
    PostInstAckReq = Xlate('RDS', RDSNo, 'POST_INST_ACK_REQ', 'X')
    If PostInstAckReq EQ True$ then
        ErrorMsg = 'The POST stage engineering instructions must be acknowledged before the load operation can be signed.'
        Error_Services('Set', ErrorMsg)
        Response = False$
        return
    end
    
        // Add check for supplement signatures
    UnacknowledgedSupp = Supplement_Services('UnacknowledgedSupplementCheck', 'RDS', RDSNo, 'POST')
    If UnacknowledgedSupp NE FALSE$ then
        ErrorMessage = 'The post epi stage supplements must be acknowledged before the post epi operation can be signed.'
        Error_Services('Set', ErrorMessage)
        Response = False$
        return    
    end
    
end service


Service UpdateFailedWafers(WOMatKey)

    WONo   = Field(WOMatKey, '*', 1)
    CassNo = Field(WOMatKey, '*', 2)
    PSNo   = Xlate('WO_LOG', WONo, 'PS_NO', 'X')
    
    If PSNo NE '' then
        
        ReactorType = Xlate('PROD_SPEC', PSNo, 'REACTOR_TYPE', 'X')
        
        If ReactorType NE '' then
            
            Begin Case
                Case ReactorType EQ 'EPP' or ReactorType EQ 'P'
                    // Epipro Silicon
                    // N/A
                    Null
                    
                Case ReactorType EQ 'GAN'
                    // Gallium Nitride
                    // N/A
                    Null
                    
                Case Otherwise$
                    // Non-Epipro Silicon
                    // Check RDS' pre, first (FWI), unload (LWI), and post cleans
                    RDSNo    = Xlate('WO_MAT', WOMatKey, 'RDS_NO', 'X')
                    WOMatRec = Database_Services('ReadDataRow', 'WO_MAT', WOMatKey)
                    If RDSNo NE '' then
                        
                        AllFailedWafers = ''
                        PostSupercede   = Xlate('RDS', RDSNo, 'POST_SUPERCEDE', 'X')
                        If PostSupercede then
                            PostCINo        = Xlate('RDS', RDSNo, 'POST_CI_NO', 'X')
                            AllFailedWafers = Xlate('CLEAN_INSP', PostCINo, 'FAILED_WAFERS', 'X')
                        end else
                            PreCINo          = Xlate('RDS', RDSNo, 'PRE_CI_NO', 'X')
                            PreFailedWafers  = Xlate('CLEAN_INSP', PreCINo, 'FAILED_WAFERS', 'X')
                            FWICINo          = Xlate('RDS', RDSNo, 'FWI_CI_NO', 'X')
                            FWIFailedWafers  = Xlate('CLEAN_INSP', FWICINo, 'FAILED_WAFERS', 'X')
                            LWICINo          = Xlate('RDS', RDSNo, 'LWI_CI_NO', 'X')
                            LWIFailedWafers  = Xlate('CLEAN_INSP', LWICINo, 'FAILED_WAFERS', 'X')
                            PostCINo         = Xlate('RDS', RDSNo, 'POST_CI_NO', 'X')
                            PostFailedWafers = Xlate('CLEAN_INSP', PostCINo, 'FAILED_WAFERS', 'X')
                            For WfrIndex = 1 to 25
                                AllFailedWafers<0, WfrIndex> = (PreFailedWafers<0, WfrIndex> OR FWIFailedWafers<0, WfrIndex> OR LWIFailedWafers<0, WfrIndex> OR PostFailedWafers<0, WfrIndex>) 
                            Next WfrIndex                            
                        end
                        AllFailedWafersOrig = WOMatRec<WO_MAT_FAILED_WAFERS$>
                        If AllFailedWafers NE AllFailedWafersOrig then 
							Transaction_Services('PostWriteFieldTransaction', 'WO_MAT', WOMatKey, WO_MAT_FAILED_WAFERS$, AllFailedWafers)
						end
                    end else
                        Error_Services('Add', 'Error in ':Service:' service. RDSNo is null')
                    end
            End Case
        end else
            Error_Services('Add', 'Error in ':Service:' service. ReactorType is null')
        end
    end else
        Error_Services('Add', 'Error in ':Service:' service. PSNo is null')
    end
    
end service


Service GetNCRRequired(WOMatKey)

    // Verify that NCR wafer quantity meets or exceeds the number of failed wafers.
    NCRReq = False$
    
    If WOMatKey NE '' then
        WONo = Field(WOMatKey, '*', 1)
        PSNo = Xlate('WO_LOG', WONo, 'PS_NO', 'X')
        
        If PSNo NE '' then
            
            ReactorType = Xlate('PROD_SPEC', PSNo, 'REACTOR_TYPE', 'X')
            
            If ReactorType NE '' then
                
                Begin Case
                    Case ReactorType EQ 'EPP' or ReactorType EQ 'P'
                        // Epipro Silicon
                        
                    Case ReactorType EQ 'GAN'
                        // Gallium Nitride
                        // No NCR verification at the moment
                        Null
                        
                    Case Otherwise$
                        // Non-Epipro Silicon
                        RDSKey    = Xlate('WO_MAT', WOMatKey, 'RDS_NO', 'X')
                        
                        LWICIKey      = Xlate('RDS', RDSKey, 'LWI_CI_NO', 'X')
                        PostSupercede = Xlate('RDS', RDSKey, 'POST_SUPERCEDE', 'X')
                        CIKey         = LWICIKey
                        If PostSupercede then 
                            PostCIKey     = Xlate('RDS', RDSKey, 'POST_CI_NO', 'X')
                            If PostCIKey NE '' then
                                CIKey = PostCIKey
                            end
                        end else
                            CIKey = LWICIKey
                        end
                        
                        NumFailedWfrs = Sum(Xlate('CLEAN_INSP', CIKey, 'FAILED_WAFERS', 'X'))
                        NumNCRWfrs    = Xlate('WO_MAT', WOMatKey, 'REJ_WFRS', 'X')
                        If NumNCRWfrs EQ '' then NumNCRWfrs = 0
                        If NumNCRWfrs LT NumFailedWfrs then NCRReq = True$
                        
                End Case
            end else
                Error_Services('Add', 'Error in ':Service:' service. ReactorType is null.')
            end
        end else
            Error_Services('Add', 'Error in ':Service:' service. PSNo is null.')
        end
    end else
        Error_Services('Add', 'Error in ':Service:' service. WOMatKey is null.')
    end
    
    Response = NCRReq
    
end service


//----------------------------------------------------------------------------------------------------------------------
// SignFQAStage
//
// Input:
//  RDSNo     - RDS to be signed
//  Username  - LSL Username of the operator signing the stage.
//
// The purpose of this service is to encapsulate the sign button click event of the RDS_POST_EPI form. Ultimately
// this service will be used in the barcode integration application so that we can decouple the sign event from
// the OpenInsight form interface. Once this service returns to the calling procedure, Error_Services('NoError') should
// be called to check if an error was set, which would have prevented the stage from being signed successfully.
//----------------------------------------------------------------------------------------------------------------------
Service SignFQAStage(RDSNo, Username)
    
    Database_Services('ActivateRecord', 'RDS', RDSNo)
    If Error_Services('NoError') then
        Valid       = ''    
        CurRdsNo    = RDSNo
        WONo        = {WO}
        CassNo      = {CASS_NO}
        WOStep      = {WO_STEP}
        ReactorType = {REACTOR_TYPE}

        ******************************************
        * Update the form with signature, if any *
        ******************************************
        CurrSig  = ''
        CurrDTM  = ''
        CurrDate = ''
        CurrTime = ''
        SupSig   = {SUP_VER_SIG}

        IF ((ReactorType NE 'EPP') AND (SupSig = '')) THEN
            WOMatRec        = XLATE('WO_MAT',WONo:'*':CassNo,'','X')
            WOMatSigProfile = WOMatRec<WO_MAT_SIG_PROFILE$>
            SigAction       = WOStep:'QA'
            LOCATE SigAction IN WOMatSigProfile USING @VM SETTING Pos THEN
                IF WOMatRec<WO_MAT_SIGNATURE$,Pos> NE '' THEN
                    CurrSig  = WOMatRec<WO_MAT_SIGNATURE$,Pos>
                    CurrDTM  = OCONV(WOMatRec<WO_MAT_SIG_DTM$,Pos>,'DT/4^HS')
                    CurrDate = CurrDTM[1,' ']
                    CurrTime = CUrrDTM[COL2()+1,' ']
                    {SUP_VER_SIG}      = CurrSig
                    {SUP_VER_SIG_DATE} = CurrDate
                    {SUP_VER_SIG_TIME} = CurrTime
                    Database_Services('WriteDataRow', 'RDS', RDSNo, @Record, True$, False$, False$)
                END
            END
        END

        *********************************************
        * Verify if the FQA has already been signed *
        *********************************************
        IF Not(MemberOf(Username,'BYPASS')) THEN
            IF (CurrSig NE '') THEN
                ErrorMsg = 'Supervisor Verification for this material has already been signed.'
                Error_Services('Add', ErrorMsg)
                return
            END
        END

        ******************************************
        * Verify if the Post-EPI has been signed *
        ******************************************
        PostEpiSig = {POST_EPI_SIG}
        IF (PostEpiSig = '') THEN
            ErrorMsg = 'Process Error: Technician Signature Required before Final QA Verification.'
            Error_Services('Add', ErrorMsg)
            return
        END

        **************************************************************************
        * Verify if the FQA signature is a different signature than the Post-EPI *
        **************************************************************************
        IF Not(MemberOf(Username,'BYPASS')) THEN        ;* Bypass requirement for a different signer for post-epi and FQA -dkk 10/9/14
            IF (PostEpiSig EQ Username) THEN
                ErrorMsg  = 'Process Error: User ':QUOTE(PostEpiSig):' has signed this RDS for Post-Epi.' |
                          : 'Another qualified user must sign FQA verification'
                Error_Services('Add', ErrorMsg)
                return
            END
        END

        ***************************************
        * Verify Metrology has been completed *
        ***************************************
        Set_Status(0)
        obj_WO_Mat('MQAComp',WONo:'*':CassNo:@RM:WOStep:@RM:'QA')
        IF Get_Status(errCode) THEN
            ErrorMsg = 'Error calling Obj_WO_Mat("MQAComp") in ':Service:'. Error code: ':errCode
            Error_Services('Add', ErrorMsg)
            return
        END

        **********************************************
        * Verify Signatures Profile has been fulfill *
        **********************************************
        WOMatKey = WONo:'*':CassNo
        Signature_Services('CheckSigOrder', WOMatKey, 'QA')
        If Error_Services('HasError') then
            Response = False$
            Return
        end

        Signature_Services('FQAReady', WOMatKey)
        If Error_Services('HasError') then
            Response = False$
            Return
        end

        WOMatRec = ''
        WOMatKey = WONo:'*':CassNo:@RM:WOStep
        WOMatRec = XLATE('WO_MAT', WOMatKey, '', 'X')
        IF (WOMatRec NE '') THEN
            ProfileSignatureCnt = COUNT(WOMatRec<WO_MAT_SIG_PROFILE$>,@VM) + (WOMatRec<WO_MAT_SIG_PROFILE$> NE '')
            FOR Index = 1 TO ProfileSignatureCnt-1 /* Minus -1 = Do not look at the last signature, which is Final QA */
                IF (WOMatRec<WO_MAT_SIG_PROFILE$,Index>[1,1] = WOStep) THEN
                    SigProfile = WOMatRec<WO_MAT_SIG_PROFILE$,Index>
                    Signature  = WOMatRec<WO_MAT_SIGNATURE$,Index>
                    SigDTM     = WOMatRec<WO_MAT_SIG_DTM$,Index>
                    SigVer     = WOMatRec<WO_MAT_SIG_VER$,Index>
                    IF ((Signature = '') OR (SigDTM = '') OR (SigVer = '')) THEN
                        ErrorMsg = 'Unable to sign FQA due to incomplete Signature Profile at ':SigProfile:' stage.'
                        Error_Services('Add', ErrorMsg)
                        return
                    END
                END
            NEXT Index
        END
        
        *************************
        * Verify Wafer Quantity *
        *************************
        CassSchedWafers = {CASS_SHIP_QTY}
        WafersOut       = {WFRS_OUT}
        
        If CassSchedWafers NE WafersOut then
            
            If NOT( MemberOf(Username, 'LEAD') OR MemberOf(Username, 'SUPERVISOR') ) then
                ErrorMsg    = 'Unable to sign FQA because the Cass Sched. Qty does not equal the ' |
                            : 'Wafers Out Qty. A lead or supervisor must override.'
                Error_Services('Add', ErrorMsg)
                return
            end else
                {WAFER_OUT_SIG}  = Username
                {WAFER_OUT_DATE} = Date()
                {WAFER_OUT_TIME} = Time()
                Database_Services('WriteDataRow', 'RDS', RDSNo, @Record, True$, False$, False$)
            end
                    
        end
        
        *********************************
        * Verify NCR total >= USL Fails *
        *********************************
		NCRStatus = QA_Services('GetNCRStatus', RDSNo)
		If NCRStatus EQ False$ then            
			If NOT( MemberOf(Username, 'LEAD') OR MemberOf(Username, 'SUPERVISOR') ) then
				ErrorMsg = 'Unable to sign FQA because the total quantity of NCR wafers is less than the '|
						 : 'number of wafers above the USL threshold. A lead or supervisor must override.'
				Error_Services('Add', ErrorMsg)
				return
			end
		end
            
        *************************
        * Verify if Shift exist *
        *************************
        IF {SHIFT} EQ '' THEN
            ErrorMsg = 'Process Error: Shift is required before signing.'
            Error_Services('Add', ErrorMsg)
            return
        END

        ****************************
        * Verify if lot is on Hold *
        ****************************
        WOMatCurrStatus = obj_WO_Mat('CurrStatus',WONo:'*':CassNo)
        IF WOMatCurrStatus = 'HOLD' THEN
            ErrorMsg = 'Process Error: Cassette is on Hold and may not be signed off.'
            Error_Services('Add', ErrorMsg)
            return
        END

        **********************
        * Check ROTR Failure *
        **********************
        ROTRAction = {ROTR_ACTION}
        If (ROTRAction NE 'P') AND (ROTRAction NE 'A') AND (ROTRAction NE '') then
            ErrorMsg = 'Process Error: ROTR does not meet all requirements.'
            Error_Services('Add', ErrorMsg)
            return
        end

        ***************************************
        * Verify Metrology has been completed *
        ***************************************
        MetTest   = {MET_TEST_QA}
        MetMin    = {MET_MIN_QA}
        MetMax    = {MET_MAX_QA}
        MetResult = {MET_RESULT_QA}
        MetSig    = {MET_SIG_QA}
        
        For each Test in MetTest using @FM setting fPos
            If Test NE '' then
                Begin Case
                    Case (MetMin<fPos> EQ '') and (MetMax<fPos> EQ '')
                        Null
                    Case (MetResult<fPos> EQ '')
                        ErrorMsg = 'Process Error: Required QA Metrology results have not been entered.'
                        Error_Services('Add', ErrorMsg)
                        return
                    Case (MetResult<fPos> LT MetMin<fPos>) or (MetResult<fPos> GT MetMax<fPos>)
                        ErrorMsg = 'Process Error: One or more QA Metrology results is out of specification.'
                        Error_Services('Add', ErrorMsg)
                        return
                    Case (MetResult<fPos> NE '') and (MetSig<fPos> EQ '')
                        ErrorMsg = 'Process Error: One or more QA Metrology results are not signed off.'
                        Error_Services('Add', ErrorMsg)
                        return
                End Case
            end       
        Next Test
        
        ******************************************
        * Verify Unload Stage QA Metrology Tests * 
        ******************************************
		WONo        = {WO}
		CassNo      = {CASS_NO}
		WOMatQAKey  = WONo : '*' : CassNo
		WOMatQARec  = Database_Services('ReadDataRow', 'WO_MAT_QA', WOMatQAKey)
		OutOfSpec   = WOMatQARec<WO_MAT_QA_OUT_OF_SPEC$>
		OutOfSpec   = Sum(OutOfSpec)
		If OutOfSpec GT 0 then
			ErrorMsg = 'Process Error: One or more Unload QA Metrology results are out of specification.'
			Error_Services('Add', ErrorMsg)
			return
		end

        *******************************************
        * Verify if all steps have been completed *
        *******************************************
        PsnNo     = {PROD_SPEC_ID}
        WONo      = {WO}
        WOStep    = {WO_STEP}
        CassNo    = {CASS_NO}
        Reactor   = {REACTOR}
        WOMatRec  = XLATE('WO_MAT',WONo:'*':CassNo,'','X')
        RDSNos    = WOMatRec<WO_MAT_RDS_NO$>
        MakeupBox = WOMatRec<WO_MAT_MAKEUP_BOX$>

        LOCATE RDSNo IN RDSNos USING @VM SETTING Pos THEN
            NextRDS = RDSNos<1,Pos+1>
        END ELSE
            NextRDS = ''
        END
                
        IF (WOMatCurrStatus NE 'QA' AND WOMatCurrStatus NE 'PSTI' AND WOMatCurrStatus NE 'COMP') AND (NextRDS NE '' AND WOMatCurrStatus NE 'VER') THEN
            ErrorMsg  = 'Process Error: Run is not ready for final QA.' |
                      : 'The ':OCONV(WOMatCurrStatus,'[WO_MAT_CURR_STATUS_CONV]'):' step is not complete.'
            Error_Services('Add', ErrorMsg)
            return
        END
            
        IF NOT(MemberOF( Username, 'SUPERVISOR' ) OR MemberOf( Username, 'LEAD' ) OR MemberOf( Username, 'FINAL_QA' )) THEN
            ErrorMsg = 'Process Error: You must be a supervisor to verify this RDS.'
            Error_Services('Add', ErrorMsg)
            return
        END
            
        ************************************************************************
        * Prompt user to validate the Process Specification Stage Instructions *
        ************************************************************************
        PSNo    = {PROD_SPEC_ID}
        VerInst = XLATE('PRS_STAGE',PSNo:'*QA',PRS_STAGE_INST$,'X')
        VerInst = TRIM(VerInst)
        IF (LEN(VerInst) > 5) THEN
            ErrorMsg = 'RDS Verification: Special engineering instructions must be acknowledged.'
            Error_Services('Add', ErrorMsg)
            return
        END

        **************************
        * Apply User's Signature *
        **************************
                                        
        SigDt = OCONV( Date(), 'D2/' )
                
        SigTm         = Time()
        SigTmPlusOne  = OCONV(SigTm + 1, 'MTS' )
        SigTmPlusTwo  = OCONV(SigTm + 2, 'MTS' )
        SigTmPlusFive = OCONV(SigTm + 5, 'MTS' )
        SigTmPlusTen  = OCONV(SigTm + 10, 'MTS' )
        SigTm         = OCONV(SigTm,'MTS')    
        SigBy         = Username    
        WOStep        = {WO_STEP}   
          
        EventParms = ''
        EventCnt   = 1
                              
        IF ReactorType NE 'EPP' THEN
        
            ToolID        = ''
            WHCd        = 'CR'
            LocCD        = 'QA'
            Tag            = ''
            
            ;* 4/30/2013 JCH added parms for merging of two methods    
            owmParms = WONo:@RM:CassNo:@RM:WOStep:@RM:'QA':@RM:Username:@RM:SigDt:' ':SigTm:@RM:ToolID:@RM:WHCd:@RM:LocCd:@RM:Tag 
            
            IF Get_Status(errCode) THEN
                ErrorMsg = 'Error calling Obj_WO_Mat("SetSignature") within ':Service:'. Error code: ':errCode
                Error_Services('Add', ErrorMsg)
                return                    ;* Added 4/4/2013 JCH
            END
            
            EventParms<COL$LOG_FILE> = 'WO_MAT'
            EventParms<COL$LOG_DTM>  = SigDt:' ':SigTm
            EventParms<COL$ACTION>   = WOStep:'QA'
            EventParms<COL$WH_CD>    = 'CR'
            EventParms<COL$LOC_CD>   = 'QA'
            EventParms<COL$WO_NOS>   = WONo
            EventParms<COL$CASS_NOS> = CassNo
            EventParms<COL$USER_ID>  = Username
            EventParms<COL$TAGS>     = ''
            EventParms<COL$TOOL_ID>  = ''
            
            CONVERT @FM TO @RM IN EventParms
            
            obj_WO_Mat_Log('Create',EventParms)        ;* * * * * INV EVENT LOG * * * * *
            
            IF Get_Status(errCode) THEN
                ErrorMsg = 'Error calling Obj_WO_Mat_Log("Create") within ':Service:'. Error code: ':errCode
                Error_Services('Add', ErrorMsg)
                return
            END
            
            IF MakeupBox = 1 THEN
                
                EventParms<COL$LOG_FILE> = 'WO_MAT'
                EventParms<COL$LOG_DTM>  = SigDt:' ':SigTmPlusTwo
                EventParms<COL$ACTION>   = 'RTU'
                EventParms<COL$WH_CD>    = 'CR'
                EventParms<COL$LOC_CD>   = 'MU'
                EventParms<COL$WO_NOS>   = WONo
                EventParms<COL$CASS_NOS> = CassNo
                EventParms<COL$USER_ID>  = Username
                EventParms<COL$TAGS>     = ''
                EventParms<COL$TOOL_ID>  = ''
                
                CONVERT @FM TO @RM IN EventParms
                
                obj_WO_Mat_Log('Create',EventParms)        ;* * * * * INV EVENT LOG * * * * *
                
                IF Get_Status(errCode) THEN
                    ErrorMsg = 'Error calling Obj_WO_Mat_Log("Create") within ':Service:'. Error code: ':errCode
                    Error_Services('Add', ErrorMsg)
                    return
                END
                
            END
            
        END    ;* End of check for non EpiPRO reactor

        {SUP_VER_SIG}      = Username
        {SUP_VER_SIG_DATE} = IConv(SigDt, 'D')
        {SUP_VER_SIG_TIME} = IConv(SigTmPlusOne, 'MT')
        
        /* Sync up the RDS record with WO_MAT */
        Database_Services('WriteDataRow', 'RDS', RDSNo, @Record, True$, False$, False$)
            
        WOMatKey = WONo:'*':CassNo
        WOMatRec = XLATE('WO_MAT',WOMatKey,'','X')
        WMRDSNos = WOMatRec<WO_MAT_RDS_NO$>
                        
        LOCATE RDSNo IN WMRDSNos USING @VM SETTING Pos THEN
            NextRDSNo = WMRDSNos<1,Pos+1>
                            
            IF NextRDSNo NE '' THEN
                CurrWfrQty = obj_WO_Mat('CurrWaferCnt',WOMatKey:@RM:WOMatRec)
                Set_Status(0)
                obj_RDS('SetSchedWfrQty',NextRDSNo:@RM:CurrWfrQty)
                IF Get_Status(errCode) THEN
                    ErrorMsg = 'Error calling Obj_RDS("SetSchedWfrQty") within ':Service:'. Error code: ':errCode
                    Error_Services('Add', ErrorMsg)
                end
            END
        END
            
        * Added 08/05/2013 JCH
        
        WOMatKey  = WONo:'*':CassNo
        OrgMUPart = XLATE('WO_MAT',WOMatKey,'MU_PART_NO','X')   
        NewMUPart = XLATE('WO_MAT',WOMatKey,'MU_PART_NO','X')
        
        IF NewMUPart NE OrgMUPart THEN

            IndexTransactionRow = 'MU_PART_NO':@FM:WOMatKey:@FM:OrgMUPart:@FM:NewMUPart:@FM
            
            OPEN "!WO_MAT" TO BangTable THEN

                LOCK BangTable, 0 THEN

                    READ PendingTrans FROM BangTable, 0 ELSE PendingTrans = '0':@FM
                    
                    PendingTrans := IndexTransactionRow
                    
                    WRITE PendingTrans ON BangTable, 0 ELSE
                        ErrorMsg = 'Unable to write index transaction to !WO_MAT. ':WOMatKey
                        Error_Services('Add', ErrorMsg)
                    END
                    UNLOCK BangTable, 0 ELSE
                        ErrorMsg = 'Unable to Open !WO_MAT to add index transaction. ':WOMatKey
                        Error_Services('Add', ErrorMsg)
                    end
                END ELSE
                    ErrorMsg = 'Unable to Lock !WO_MAT to add index transaction. ':WOMatKey
                    Error_Services('Add', ErrorMsg)
                END
            END ELSE
                ErrorMsg = 'Unable to Open !WO_MAT to add index transaction. ':WOMatKey
                Error_Services('Add', ErrorMsg)
            END
        END    ;* End of check for changed index value
        
        * End of 10/14/2010 update
    end else
        ErrorMsg = 'Error reading RDS record: ':RDSNo:' in service: ':Service
        Error_Services('Add', ErrorMsg)
    end
    
end service

Service UnsignPreEpiReady(RDSNo)
    
    ErrorMsg = ''
    UnsignPreEpiReady = False$
    
    If RDSNo NE '' then
        If RowExists('RDS', RDSNo) then
            If Error_Services('NoError') then
                //Call on the same logic to determine if the unsign LOAD signature is ready
                UnsignLoadReady = QA_Services('UnsignLoadSignatureReady', RDSNo)
                if Error_Services('NoError') then
                    If UnsignLoadReady then
                        //Check if LOAD signature is present
                        LoadSig = Database_Services('ReadDataColumn', 'RDS', RDSNo, RDS_OPERATOR_IN$, True$, 0, False$)
                        If Error_Services('NoError') then
                            If LoadSig EQ '' then
                                UnsignPreEpiReady = True$
                            end else
                                ErrorMsg = 'Load signature is set.'
                            end
                        end else
                            ErrorMsg = Error_Services('GetMessage')
                        end
                    end else
                        ErrorMsg = 'A signature beyond pre-epi exists. Please check later stages of this RDS.'
                    end
                end else
                    ErrorMsg = Error_Services('GetMessage')
                end
            end else
                ErrorMsg = Error_Services('GetMessage')
            end
        end else
            ErrorMsg = 'RDS ' : RDSNo : ' was not found in database.'
        end
    end else
        ErrorMsg = 'RDS No was null'
    end
    
    If ErrorMsg NE '' then
        Error_Services('Add', ErrorMsg)
    end
    
    Response = UnsignPreEpiReady
    
end service

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

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











