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

    This program is proprietary and is not to be used by or disclosed to others, nor is it to be copied without written
    permission from SRP Computer Solutions, Inc.

    Name        :   Scan_Services

    Description :   Handler program for all Scan services.

    Notes       :   

    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)
        09/07/18    dmb     Original programmer.

***********************************************************************************************************************/

#pragma precomp SRP_PreCompiler

$insert LOGICAL
$insert SERVICE_SETUP
$insert SCANS_EQUATES
$insert RDS_EQUATES
$insert RDS_LAYER_EQUATES
$insert DICT_EQUATES
$insert WO_MAT_EQUATES
$insert OVERRIDE_LOG_EQUATES
$insert REACTOR_EQUATES
$INSERT REACT_LL_EQUATES
$insert RLIST_EQUATES
$Insert ROTR_OVERRIDE_COMMENT_OPTIONS_EQUATES

Common /ScanServices/ NotAcceptableReasons@, Unused2@, Unused3@, Unused4@, Unused5@, Unused6@, Unused7@, Unused8@

Declare Function    Scan_Services, Memory_Services, Database_Services, SRP_JSON, RTI_CreateGUID, Rds_Services, Datetime
Declare Function    QA_Services, Error_Services, Security_Services, SRP_Array, obj_WO_Mat, Memberof, Override_Log_Services
Declare Function    Keyboard_Sim_Services, Environment_Services, Logging_Services, Reactor_Services
Declare Subroutine  Scan_Services, Memory_Services, Database_Services, SRP_JSON, QA_Services, Error_Services
Declare Subroutine  obj_WO_Mat_Log, Tool_Parms_Services, RDS_Services, Logging_Services

Equ CRLF$		to \0D0A\

LogPath     = Environment_Services('GetApplicationRootPath') : '\LogFiles\ScanAPI'
LogDate     = Oconv(Date(), 'D4/')
LogTime     = Oconv(Time(), 'MTS')
LogFileName = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' Load-Unload Log.csv'
Headers     = 'Logging DTM' : @FM : 'RDS Key ID' : @FM : 'User' : @FM : 'CurrStage' : @FM : 'Scan ID' : @FM : 'Action' : @FM : 'Result'
objLog      = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, Comma$, Headers, '', False$, False$)
LoggingDTM  = LogDate : ' ' : LogTime   ; // Logging DTM

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

Return Response OR ''


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Service Parameter Options
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Options BOOLEAN         = True$, False$
Options SCAN_TYPES      = 'LOCATION', 'TOOL'


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


Service AddNotAcceptableReason(Reason)
    
    If Len(NotAcceptableReasons@) then
        NotAcceptableReasons@  := @FM : Reason
    end else
        NotAcceptableReasons@  = Reason
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// GetNotAcceptableReason
//
// Returns the most current not acceptable reason.
//----------------------------------------------------------------------------------------------------------------------
Service GetNotAcceptableReason()

    Response = NotAcceptableReasons@[-1, 'B' : @FM]

End Service


//----------------------------------------------------------------------------------------------------------------------
// GetNotAcceptableReasons
//
// Returns the stack of not acceptable reasons. This will be @FM delimited.
//----------------------------------------------------------------------------------------------------------------------
Service GetNotAcceptableReasons()

    Response = NotAcceptableReasons@

End Service


//----------------------------------------------------------------------------------------------------------------------
// ClearNotAcceptableReasons
//
// Clears all not acceptable reasons.
//----------------------------------------------------------------------------------------------------------------------
Service ClearNotAcceptableReasons()

    NotAcceptableReasons@  = ''

End Service


//----------------------------------------------------------------------------------------------------------------------
// HasNotAcceptableReason
//
// Returns True if there is an error condition, False if there is no error condition. Caller will still need to use
// the GetMessage or GetMessages service to determine what the error is. The HasError service allows the caller to
// embed the Error_Services service call inside of a conditional statement like this:
//
// If Error_Services('HasError') then
//     * An error has occured. Proceed accordingly.
//     ErrorMessage = Error_Services('GetMessage')
// end else
//     * No error has occured.
// end
//----------------------------------------------------------------------------------------------------------------------
Service HasNotAcceptableReason()

    If Len(NotAcceptableReasons@) then
        Response = True$
    end else
        Response = False$
    end

End Service


//----------------------------------------------------------------------------------------------------------------------
// NoNotAcceptableReason
//
// Returns True if there are no error conditions, False if there is an error condition. This is the opposite of the
// HasError service and exists for improved readability.
//----------------------------------------------------------------------------------------------------------------------
Service NoNotAcceptableReason()

    If Len(NotAcceptableReasons@) then
        Response = False$
    end else
        Response = True$
    end

End Service


//----------------------------------------------------------------------------------------------------------------------
// CreateScansRow
//
// Creates a new database row in the SCANS table. Retruns the Key ID to the SCANS table if successful.
//----------------------------------------------------------------------------------------------------------------------
Service CreateScansRow(ScanType=SCAN_TYPES)

    ScanID  = RTI_CreateGUID()

    If ScanID NE '' then
        ScansRow                        = ''
        ScansRow<SCANS.CREATED_DATE$>   = Date()
        ScansRow<SCANS.CREATED_TIME$>   = Time()
        ScansRow<SCANS.SCAN_TYPE$>      = ScanType
        ScansRow<SCANS.ACCEPTED$>       = False$
        Database_Services('WriteDataRow', 'SCANS', ScanID, ScansRow, True$, False$, True$)
    end else
        Error_Services('Add', 'ScanID was not created in the ' : Service : ' service.')
    end

    Response    = ScanID

end service


//----------------------------------------------------------------------------------------------------------------------
// GetScansRow
//
// Returns the database row from the SCANS table for the indicated Scan ID. The default format is MultiValue.
//----------------------------------------------------------------------------------------------------------------------
Service GetScansRow(ScanID, ReturnJSON)

    ScansRow    = ''

    If ScanID NE '' then
        ScansRow    = Database_Services('ReadDataRow', 'SCANS', ScanID)
        If ReturnJSON EQ True$ then
            ScansRow    = Scan_Services('ConvertMVScanToJSON', ScanID, ScansRow)
        end
    end else
        Error_Services('Add', 'ScanID argument was missing in the ' : Service : ' service.')
    end

    Response    = ScansRow

end service


//----------------------------------------------------------------------------------------------------------------------
// SetScansRow
//
// Returns the database row from the SCANS table for the indicated Scan ID. The default format is MultiValue.
//----------------------------------------------------------------------------------------------------------------------
Service SetScansRow(ScanID, ScansRow)

    If (ScanID NE '') AND (ScansRow NE '') then
        Database_Services('WriteDataRow', 'SCANS', ScanID, ScansRow)
    end else
        Error_Services('Add', 'ScanID or ScansRow argument was missing in the ' : Service : ' service.')
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// ProcessScanData
//
// Processes new scan data and updates the SCANS database row for the indicated Scan ID. This service should parse the
// scan data and identify its intended field and then evaluate if the scan data is valid. Note: just because the scan
// resource is not ready to be accepted, it does not mean this scan data is invalid. Each scan data will have to be
// evaluated on a case by case basis.
//----------------------------------------------------------------------------------------------------------------------
Service ProcessScanData(ScanID, ScanJSON)

    If ( (ScanID NE '') AND (ScanJSON NE '') ) then
        hScanJSON     = ''
        ParseResponse   = SRP_JSON(hScanJSON, 'PARSE', ScanJSON)
        If (ParseResponse EQ '') then

            ScanData    = SRP_JSON(hScanJSON, 'GetValue', 'scanData')                     
            // Code 3of9 encodes the asterisk and underscore characters. These need to be decoded.
            If ScanData NE '' then Scan_Services('DecodeScanData', ScanData)
            // Confirm that this is a valid Scan ID.
            ScansRow    = Database_Services('ReadDataRow', 'SCANS', ScanID)
            If Error_Services('NoError') then
                // Use the system variables to evaluate calculated columns easily.
                @DICT       = Database_Services('GetTableHandle', 'DICT.SCANS')
                @ID         = ScanID
                @RECORD     = ScansRow
                Action      = ''
                ColumnIndex = ''
                ColumnValue = ''

		        // Identify the scan data based on the data identifier prefix (if any). Otherwise, assume this is a
                // type of cassette ID (i.e., RDS or WMO).
                Begin Case

					//DPC - 3/18/20 - added validation to allow 1T scans to be interpreted as RDS or Supplier Lot code
					//1T could be supplier lot (Global Wafer and Sumco Japan) or could be IFX internal lot (RDS)
					//2T is always supplier lot
                    Case ( (ScanData[1, 2] EQ '1T') or (ScanData[1, 2] EQ '2T') )
                        // Supplier lot scan.
                        Convert '_' to '-' in ScanData
                        SupplierLotID = ScanData[3, 999]
                        If ( RowExists('RDS', SupplierLotID) EQ True$ AND ScanData[1, 2] EQ '1T') then
							ScansRow<SCANS.CASSETTE_IDS$> = SupplierLotID
                        //If ( RowExists('RDS', SupplierLotID) EQ True$ ) then
                        //    Error_Services('Add', 'Not a valid supplier lot')
                        end else
                            ScansRow<SCANS.SUPPLIER_LOT$> = SupplierLotID
                        end                       
                        
                    Case ScanData[1, 5] EQ 'RESET'

                        ScansRow<SCANS.SCAN_TYPE$> = 'RESET'
                        ScansRow<SCANS.EMPLOYEE_ID$>    = ''
                        ScansRow<SCANS.LOCATION_ID$>    = ''
                        ScansRow<SCANS.TOOL_ID$>        = ''
                        ScansRow<SCANS.LOAD_LOCK$>      = ''
                        
                    Case ScanData[1, 2] EQ '1H'
                        // Employee ID scan. Set the EMPLOYEE_ID column and check the
                        // EMPLOYEE_AUTHORIZED and EMPLOYEE_NOT_AUTHORIZED_REASON column values to determine if the
                        // scan data is valid.
                        {EMPLOYEE_ID}       = ScanData[3, 999]
                        EmployeeAuthorized = {EMPLOYEE_AUTHORIZED}
                        EmployeeActive     = Xlate('LSL_USERS', {EMPLOYEE_ID}, 'ACTIVE', 'X')
                        
                        ScansRow<SCANS.AUTHENTICATED$> = 0
                        Begin Case
                            Case EmployeeAuthorized NE True$
                                // Regardless of the not authorized reason, the scan data will be considered invalid.
                                EmployeeNotAuthorizedReason = {EMPLOYEE_NOT_AUTHORIZED_REASON}
                                Error_Services('Add', EmployeeNotAuthorizedReason)
                            Case EmployeeActive NE True$
                                Error_Services('Add', 'Inactive employee.')
                            Case Otherwise$
                                ScansRow<SCANS.EMPLOYEE_ID$>    = {EMPLOYEE_ID}
                        End Case                       

                    Case ScanData[1, 3] EQ '10S'
                        
                        // Tool ID scan. A determination needs to be made to see if this is a transfer tool or a process
                        // tool. Transfer tools do not require employee authorization, but process tools do.
                        //
                        // If this is a non-existent tool or if the current Employee ID is not authorized
                        // will the scan data be considered invalid. However, if there is no Employee ID, then do not
                        // use this to invalidate the scan data.
                        ScanData    = ScanData[4, 999]
                        ToolID      = ScanData[1, '.']
                        LoadLock    = ScanData[Col2() + 1, 1]
                        ToolRow     = Database_Services('ReadDataRow', 'TOOL', ToolID)                       
                        If Error_Services('NoError') then
                            ScansRow<SCANS.SCAN_TYPE$> = 'TOOL'
                            ToolType    = ToolRow<2>
                            Begin Case
                                Case ToolType _EQC 'Reactor'
                                    // Clear the Location ID and set the Tool ID column to determine if the current Employee ID
                                    // is authorized.
                                    {LOCATION_ID}       = ''
                                    {TOOL_ID}           = ToolID
                                    EmployeeAuthorized  = {EMPLOYEE_AUTHORIZED}
                                    If (EmployeeAuthorized NE True$) AND ({EMPLOYEE_ID} NE '') then
                                        // Regardless of the not authorized reason, the scan data will be considered invalid.
                                        EmployeeNotAuthorizedReason = {EMPLOYEE_NOT_AUTHORIZED_REASON}
                                        Error_Services('Add', EmployeeNotAuthorizedReason)
                                    end else                                       
*                                        If LoadLock EQ '' then LoadLock = 'NA'  ; // NA means Not Applicable.
                                        ReactorID = ToolID[2, 999]
                                        LoadLockReq = Xlate('REACTOR', ReactorID, 'LOAD_LOCK_REQ', 'X')
                                        //LoadLockReq = Xlate('REACTOR', ReactorID, REACTOR_PICK_PLACE$, 'X')
                                        
                                        Begin Case
                                            Case (LoadLockReq EQ True$)
                                                If (LoadLock NE '') then
                                                    If ( (LoadLock EQ 'L') or (LoadLock EQ 'R') ) then                                                                                             
                                                        If ScansRow<SCANS.LOCATION_ID$> NE '' then
                                                            ScansRow<SCANS.CASSETTE_IDS$> = ''
                                                        end else
                                                            ScansRow<SCANS.CASSETTE_IDS$>   = ScansRow<SCANS.CASSETTE_IDS$, 1>  ; // Make sure only the first Cassette ID is tracked.
                                                        end
                                                        ScansRow<SCANS.LOCATION_ID$>    = ''                                ; // Make sure the Location ID is cleared.                                                       
                                                        ScansRow<SCANS.TOOL_ID$>        = ToolID
                                                        ScansRow<SCANS.LOAD_LOCK$>      = LoadLock
                                                    end else
                                                        Error_Services('Add', 'Invalid load lock value "':LoadLock:'".')
                                                    end
                                                end else
                                                    Error_Services('Add', 'A load lock side "L" or "R" is required for reactor ':ReactorID:'.')
                                                end
                                            Case (LoadLockReq EQ False$)
                                                If ScansRow<SCANS.LOCATION_ID$> NE '' then
                                                    ScansRow<SCANS.CASSETTE_IDS$> = ''
                                                end else
                                                    ScansRow<SCANS.CASSETTE_IDS$>   = ScansRow<SCANS.CASSETTE_IDS$, 1>  ; // Make sure only the first Cassette ID is tracked.
                                                end
                                                ScansRow<SCANS.LOCATION_ID$>    = ''                                ; // Make sure the Location ID is cleared.
                                                ScansRow<SCANS.TOOL_ID$>        = ToolID
                                                ScansRow<SCANS.LOAD_LOCK$>      = LoadLock
                                        End Case
                                        
                                    end
                                Case ToolType _EQC 'Transfer'
                                    Error_Services('Add', 'Tool type ':ToolType:' is not currently supported by the barcode application.')
                                    // Code below may be implemented down the road.
                                    // This is a transfer tool. Just update the scan resource.
*                                    ScansRow<SCANS.LOCATION_ID$>        = ''                                ; // Make sure the Location ID is cleared.
*                                    ScansRow<SCANS.CASSETTE_IDS$>       = ScansRow<SCANS.CASSETTE_IDS$, 1>  ; // Make sure only the first Cassette ID is tracked.
*                                    ScansRow<SCANS.TRANSFER_TOOL_ID$>   = ToolID
                                Case Otherwise$
                                    Error_Services('Add', 'Tool type ':ToolType:' is not currently supported by the barcode application.')
                            End Case
                        end else
                            Error_Services('Add', ToolID : ' is not a valid tool ID.')
                        end

                    Case ScanData[1, 2] EQ '1L'
                        // Location ID scan. Only if this is a non-existent location will the scan data be considered
                        // invalid.
                        LocationID  = ScanData[3, 999]
                        Convert '.' to '*' in LocationID
                        LocationRow = Database_Services('ReadDataRow', 'LOCATION', LocationID)
                        Convert '*' to '.' in LocationID
                        If Error_Services('NoError') then
                            ScansRow<SCANS.SCAN_TYPE$> = 'LOCATION'
                            If ScansRow<SCANS.TOOL_ID$> NE '' then ScansRow<SCANS.CASSETTE_IDS$> = ''
                            ScansRow<SCANS.TOOL_ID$>            = ''    ; // Make sure the Tool ID is cleared.
                            ScansRow<SCANS.LOAD_LOCK$>          = ''    ; // Make sure the Tool Load Lock is cleared.
                            ScansRow<SCANS.TRANSFER_TOOL_ID$>   = ''    ; // Make sure the Transfer Tool ID is cleared.
                            ScansRow<SCANS.LOCATION_ID$>        = LocationID
                        end else
                            Error_Services('Add', LocationID : ' is not a valid location ID.')
                        end

                    Case ScanData[1, 2] EQ '1B'
                        // Carrier/boat ID scan. Only if this is a non-existent carrier will the scan data be considered
                        // invalid.
                        ScanData    = ScanData[3, 999]
                        PLNo        = ScanData[1, '.']
                        BoatID      = ScanData[Col2() + 1, '.']
                        If BoatID NE '' then
                            ScansRow<SCANS.LOCATION_ID$>    = ''                                ; // Make sure the Location ID is cleared.
                            ScansRow<SCANS.CASSETTE_IDS$>   = ScansRow<SCANS.CASSETTE_IDS$, 1>  ; // Make sure only the first Cassette ID is tracked.
                            ScansRow<SCANS.BOAT_ID$>        = BoatID
                            ScansRow<SCANS.PL_NUMBER$>      = PLNo
                        end else
                            Error_Services('Add', ScanData : ' is not a valid boat ID.')
                        end
                    Case ScanData[1, 3] EQ 'PWD'
                        // Password/Signature scan. If the scan is not ready to be accepted or the password does
                        // not match the password of the employee ID scanned then this will be an invalid scan,
                        // otherwise the scan will be accepted.
                        Password = ScanData[4, 999]
                        If Password NE '' then
                            EmployeeID = ScansRow<SCANS.EMPLOYEE_ID$>
                            If EmployeeID NE '' then
                                Authenticated = Security_Services('AuthenticateLSLCredentials', EmployeeID, Password)
                                If Authenticated EQ True$ then
                                    ScansRow<SCANS.AUTHENTICATED$> = 1 
                                end else
                                    ScansRow<SCANS.AUTHENTICATED$> = 0
                                    Error_Services('Add', 'Invalid password for user ':EmployeeID:'.')
                                end
                            end else
                                Error_Services('Add', 'An employee ID must be scanned before scanning a password.')
                            end
                        end else
                            Error_Services('Add', 'Invalid password scanned.')
                        end
                    Case ScanData[1, 8] EQ 'OVERRIDE'
                        
                        Type = ScansRow<SCANS.OVERRIDE_TYPE$>
                        
                        if {EMPLOYEE_ID} NE '' AND ScansRow<SCANS.AUTHENTICATED$> EQ 1 then
                            
                            Username    = {EMPLOYEE_ID}
                            
                            
                            Member = False$
                            Groups = 'LEAD':@VM:'SUPERVISOR':@VM:'ENGINEER':@VM:'ENG_TECH':@VM:'ROTR_OVERRIDE'
                            
                            For each Group in Groups using @VM
                                Member = MemberOf(Username, Group)
                            Until Member EQ True$
                            Next Group
                            
                            if Member EQ True$ then
                                
                                IF Type EQ 'ROTR' then
                                    
                                    
                                    Reactor = {TOOL_ID}[2,999]
                                    RDSNo   = {CASSETTE_IDS}
                                    Comment = ScanData[13, 999]
                                    //Perform the Override here
                                    overrideLogTable    = 'RDS':@VM:'REACTOR'
                                    overrideLogKey      = RDSNo:@VM:Reactor
                                    overrideLogUser     = {EMPLOYEE_ID}
                                    overrideLogComment  = Comment
                                    overrideLogCategory = 'ROTR_BLOCK'
                                    overrideLogCause   = Xlate('REACTOR', Reactor, REACTOR_ROTR_STATUS_REASON$, 'X')
                                    orKey = Override_Log_Services('Create', overrideLogTable, overrideLogKey, overrideLogUser, overrideLogComment, overrideLogCategory, overrideLogCause)
                                    
                                    
                                    //Add override key to RDS
                                    RDSRec = Xlate('RDS', RDSNo, '', 'X')
                                    RDSRec = Insert(RDSRec, RDS_OVERRIDE_KEYS$, 1, 0, orKey)
                                    
                                    //Clear Set ROTR status to Passing and clear reason
                                    ReactorRec                              = XLATE('REACTOR', Reactor, '', 'X')
                                    ReactorRec<REACTOR_ROTR_STATUS$>        = 'P'
                                    
                                    rotrStatusReason = ReactorRec<REACTOR_ROTR_STATUS_REASON$>
									ReactorRec<REACTOR_PREVIOUS_ROTR_STATUS_REASON$> = rotrStatusReason
									ReactorRec<REACTOR_ROTR_STATUS_REASON$> = ''
									
									ReactorRec<REACTOR_PREVIOUS_ROTR_OVERRIDE_RDS$> = RDSNo
									
									rotrOverrideCount = ReactorRec<REACTOR_ROTR_OVERRIDE_COUNT$>
									ReactorRec<REACTOR_ROTR_OVERRIDE_COUNT$> = rotrOverrideCount + 1
                                    Database_Services('WriteDataRow', 'REACTOR', Reactor, ReactorRec, True$, False$, True$)
                                    Database_Services('WriteDataRow', 'RDS', RDSNo, RDSRec, True$, False$, True$)
                                    
                                    ScansRow<SCANS.EMPLOYEE_ID$>= ''
                                    Result = 'Override Performed successfully'
                                end
                                
                            end else
                                Error_Services('Add', 'Selected Employee is not authorized to perform an override')
                            end
                        end
                    Case ScanData[1, 2] EQ '5T'
                        //Tencor Scan - Note this is only for loading cassette ID into Tencor tool
                        ScansRow<SCANS.SCAN_TYPE$> = 'TENCOR_LOAD'
                        ScansRow<SCANS.TOOL_ID$>= ScanData[3,99]
                    Case ScanData EQ ''
                        // Most likely receiving RDS data, so nothing to do here.
                        Null
                    Case Otherwise$
                        // Assume this is intended to be a Cassette ID scan (either WMO or RDS). Only if this is a
                        // non-existent carrier will the scan data be considered invalid.
                        ColumnIndex     = SCANS.CASSETTE_IDS$
                        CassetteID      = ScanData

                        ValidCassetteID = False$  ; // Assume Cassette ID is not valid for now.
                        If Num(CassetteID) then
                            // Might be an RDS number.
                            RDSRow  = Database_Services('ReadDataRow', 'RDS', CassetteID)
                            If Error_Services('NoError') then
                                ValidCassetteID = True$
                            end
                        end else
                            // Might be a WMO number.
                            If DCount(CassetteID, '.') EQ 3 then
                                // Passes the format test.
                                If ( (CassetteID[1, 1] EQ 'O') or (CassetteID[1, 1] EQ 'I') ) then
                                    IOFlag      = CassetteID[1, 1]
                                    CassetteID  = CassetteID[2, 999]
                                end else
                                    IOFlag      = ''
                                end
                                Convert '.' to '*' in CassetteID
                                WMOutRow    = Database_Services('ReadDataRow', 'WM_OUT', CassetteID)
                                Convert '*' to '.' in CassetteID
                                If Error_Services('NoError') then
                                    ValidCassetteID = True$
                                end
                            end
                        end
                        If ValidCassetteID EQ True$ then
                            // This is a valid Cassette ID that needs to be added to this resource. If the Tool
                            // ID is not populated, then append this Cassette ID to the array, otherwise replace
                            // the existing Cassette ID with this one.
                            If ScansRow<SCANS.TOOL_ID$> EQ '' then
                                // Only append if it doesn't already exists.
                                Locate CassetteID in ScansRow<SCANS.CASSETTE_IDS$> using @VM setting vPos else
                                    ScansRow<SCANS.CASSETTE_IDS$, -1>   = CassetteID
                                end
                            end else
                                ScansRow<SCANS.CASSETTE_IDS$>   = CassetteID 
                            end
                        end else
                            Error_Services('Add', CassetteID : ' is not a valid Cassette ID.')
                        end
                End Case
                           
                If Error_Services('NoError') then
                    // Process Scan row data here to determine the type of scan (i.e. Location, Pre-Epi + Load, or Unload).
                    ScanType     = ScansRow<SCANS.SCAN_TYPE$>                    
                    CassetteIDs  = ScansRow<SCANS.CASSETTE_IDS$>
                    NumCass      = DCount(CassetteIDs, @VM)
                    EmployeeID   = ScansRow<SCANS.EMPLOYEE_ID$>     
                    Begin Case
                        Case ScanType EQ 'RESET'
                            Action = 'RESET'
                            
                        Case ScanType EQ 'TENCOR_LOAD'
                            Action = 'TENCOR_LOAD'
                            If (CassetteIDs EQ '') then
                                Scan_Services('AddNotAcceptableReason', 'At least one cassette must be scanned to complete a Tencor scan.')
                            end
                            
                        Case ScanType EQ 'LOCATION'
                            Action = 'PLACE'
                            If (EmployeeID EQ '') then
                                Scan_Services('AddNotAcceptableReason', 'EmployeeID required to complete a location scan.')                      
                            end
                            If (CassetteIDs EQ '') then
                                Scan_Services('AddNotAcceptableReason', 'At least one cassette must be scanned to complete a location scan.')
                            end
                            LastCassScanned = CassetteIDs<0, NumCass>
                            CurrStage = Xlate('RDS', LastCassScanned, 'CURR_STAGE', 'X')
                            If CurrStage EQ 'UNLOAD' then
                                Error_Services('Add', '(':LastCassScanned:') Cassette is currently loaded on a tool and is ineligble for a location scan.')
                            end
                                                                                   
                        Case ScanType EQ 'TOOL'
                            // Check if cassette field is populated. If so, then check the cassette's current status to
                            // determine what the next action will be (i.e. Pre-Epi+Load, Unload, etc.). Ensure we are
                            // working with an RDS and not a WM_IN or WM_OUT cassette ID. They are not used during the
                            // "RDS" process.

                            If (CassetteIDs NE '') then                           
                                // Ensure only one cassette is scanned
                                If (NumCass EQ 1) then
                                    CassetteID = CassetteIDs<0, NumCass>                         
                                    // Ensure we are working with an RDS and not a WM_OUT/WM_IN cassette
                                    If (Count(CassetteID, '.') EQ 0) then

                                        RDSNo   = CassetteID
                                        RDSRec  = Database_Services('ReadDataRow', 'RDS', RDSNo)
                                        
                                        WONo        = RDSRec<RDS_WO$>
                                        CassNo      = RDSRec<RDS_CASS_NO$>
                                        CurrStatus  = obj_WO_Mat('CurrStatus', WONo:'*':CassNo)
                                        
                                        // RDS Format
                                        IsEpiPro = RDS_Services('IsEpiPro', CassetteID)
                                        If IsEpiPro EQ False$ then
                                            // Non-EpiPro RDS
                                            WONo        = Xlate('RDS', CassetteID, 'WO', 'X')
                                            CassNo      = Xlate('RDS', CassetteID, 'CASS_NO', 'X')
                                            WOMatKey    = WONo:'*':CassNo
                                            WOMatLocs   = Xlate('WO_MAT', WOMatKey, WO_MAT_INV_LOCATION$, 'X')
                                            If Index(WOMatLocs,'PTI',1) else Error_Services('Add', 'Cassette ':CassetteID:' failed due to missing PTI.')                                      
                                        end else
                                            // EpiPro RDS                                           
                                            OutCassNos = Xlate('RDS', CassetteID, 'OUT_CASS_NO', 'X')
                                            OutCassNos = SRP_Array('Clean', OutCassNos, 'TrimAndMakeUnique', @VM)
                                            For each OutCassNo in OutCassNos using @VM setting oPos
                                                WONo        = Xlate('RDS', CassetteID, 'WO', 'X')
                                                CassNo      = OutCassNo
                                                WOMatKey    = WONo:'*':CassNo
                                                WOMatLocs   = Xlate('WO_MAT', WOMatKey, WO_MAT_INV_LOCATION$, 'X')
                                                If Index(WOMatLocs,'PTI',1) else Error_Services('Add', 'Cassette ':CassetteID:' failed due to missing PTI.')                                                      
                                            Next OutCassNo
                                        end     
                                        
                                        If Error_Services('NoError') then
                                            If CurrStatus NE 'HOLD' then
                                                If (EmployeeID NE '') then
                                            
                                                    SupplInstAckReq      = Xlate('RDS', RDSNo, 'SUPPL_ACK_REQ'         , 'X')                    
                                                    RDSLayerInstAckReq   = Xlate('RDS', RDSNo, 'RDS_LAYER_ACK_REQ'     , 'X')   
                                                    PreInstAckReq        = Xlate('RDS', RDSNo, 'PRE_INST_ACK_REQ'      , 'X')
                                                    FwiInstAckReq        = Xlate('RDS', RDSNo, 'FWI_INST_ACK_REQ'      , 'X')
                                                    LwiInstAckReq        = Xlate('RDS', RDSNo, 'LWI_INST_ACK_REQ'      , 'X')
                                                    QAInstAckReq         = Xlate('RDS', RDSNo, 'QA_INST_ACK_REQ'       , 'X')
                                                    LoadInstAckReq       = Xlate('RDS', RDSNo, 'LOAD_INST_ACK_REQ'     , 'X')
                                                    UnloadInstAckReq     = Xlate('RDS', RDSNo, 'UNLOAD_INST_ACK_REQ'   , 'X')
													PostInstAckReq       = Xlate('RDS', RDSno, 'POST_INST_ACK_REQ'     , 'X')
                                                    WaferCountInstAckReq = Xlate('RDS', RDSNo, 'WAFER_COUNT_ACK_REQ'   , 'X')
                                                    // Parse JSON objects
                                                    hRDS                 = SRP_JSON(hScanJSON, 'Get', 'rds')
                                                    If (hRDS NE 0) then                                        
                                                        If (SupplInstAckReq EQ True$) then
                                                            SupplAck = SRP_JSON(hRDS, 'GetValue', 'supplInstAck')
                                                            If (SupplAck EQ True$) then
                                                                RDSRec<RDS_SUPPL_ACK$>          = True$
                                                                RDSRec<RDS_SUPPL_SIG$>          = ScansRow<SCANS.EMPLOYEE_ID$>
                                                                RDSRec<RDS_SUPPL_SIG_DATE$>     = Date()
                                                                RDSRec<RDS_SUPPL_SIG_TIME$>     = Time()
                                                            end
                                                        end
                                                        If RDSLayerInstAckReq   then RDSRec<RDS_RDS_LAYER_ACK$>     = SRP_JSON(hRDS, 'GetValue', 'rdsLayerInstAck')
                                                        If PreInstAckReq        then RDSRec<RDS_PRE_INST_ACK$>      = SRP_JSON(hRDS, 'GetValue', 'preInstAck')
                                                        If FwiInstAckReq        then RDSRec<RDS_FWI_INST_ACK$>      = SRP_JSON(hRDS, 'GetValue', 'fwiInstAck')
                                                        If LwiInstAckReq        then RDSRec<RDS_LWI_INST_ACK$>      = SRP_JSON(hRDS, 'GetValue', 'lwiInstAck')
                                                        If QAInstAckReq         then RDSRec<RDS_QA_INST_ACK$>       = SRP_JSON(hRDS, 'GetValue', 'qaInstAck')
                                                        If LoadInstAckReq       then RDSRec<RDS_LOAD_INST_ACK$>     = SRP_JSON(hRDS, 'GetValue', 'loadInstAck')
                                                        If UnloadInstAckReq     then RDSRec<RDS_UNLOAD_INST_ACK$>   = SRP_JSON(hRDS, 'GetValue', 'unloadInstAck')
                                                        If PostInstAckReq       then RDSRec<RDS_POST_INST_ACK$>     = SRP_JSON(hRDS, 'GetValue', 'postInstAck')
                                                        If WaferCountInstAckReq then RDSRec<RDS_WAFER_COUNT_ACK$>   = SRP_JSON(hRDS, 'GetValue', 'waferCountInstAck')
                                                        
                                                        Database_Services('WriteDataRow', 'RDS', RDSNo, RDSRec, True$, False$, True$)
                                                        
                                                        SupplInstAckReq      = Xlate('RDS', RDSNo, 'SUPPL_ACK_REQ'         , 'X')                    
                                                        RDSLayerInstAckReq   = Xlate('RDS', RDSNo, 'RDS_LAYER_ACK_REQ'     , 'X')   
                                                        PreInstAckReq        = Xlate('RDS', RDSNo, 'PRE_INST_ACK_REQ'      , 'X')
                                                        FwiInstAckReq        = Xlate('RDS', RDSNo, 'FWI_INST_ACK_REQ'      , 'X')
                                                        LwiInstAckReq        = Xlate('RDS', RDSNo, 'LWI_INST_ACK_REQ'      , 'X')
                                                        QAInstAckReq         = Xlate('RDS', RDSNo, 'QA_INST_ACK_REQ'       , 'X')
                                                        LoadInstAckReq       = Xlate('RDS', RDSNo, 'LOAD_INST_ACK_REQ'     , 'X')
                                                        UnloadInstAckReq     = Xlate('RDS', RDSNo, 'UNLOAD_INST_ACK_REQ'   , 'X')
														PostInstAckReq       = Xlate('RDS', RDSNo, 'POST_INST_ACK_REQ'     , 'X')
                                                        WaferCountInstAckReq = Xlate('RDS', RDSNo, 'WAFER_COUNT_ACK_REQ'   , 'X')
                                                        
                                                        SRP_JSON(hRDS, 'Release')                
                                                    end
                                                                                       
                                                    ToolID          = ScansRow<SCANS.TOOL_ID$>
                                                    Reactor         = ToolID[-1, 'BR']
                                                    Username        = ScansRow<SCANS.EMPLOYEE_ID$>
                                                    WaferCountAck   = RDSRec<RDS_WAFER_COUNT_ACK$>
                                                    LLSide          = ScansRow<SCANS.LOAD_LOCK$>
                                                    WaferQty        = ''
                                                    If WaferCountAck EQ True$ then
                                                        WaferQty = RDSRec<RDS_CASS_WAFER_QTY$>
                                                    end else
                                                        WaferQty = RDSRec<RDS_VERIFY_QTY$>
                                                    end
                                                    // Check signature fields to determine where the lot is in the RDS process. The service should
                                                    // use the signatures in the RDS table, not the WO_MAT table, so that we can support both
                                                    // EpiPro and non-EpiPro lots.
                                                    WONo        = RDSRec<RDS_WO$>
                                                    CassNo      = RDSRec<RDS_CASS_NO$>
                                                    CurrStatus  = obj_WO_Mat('CurrStatus', WONo:'*':CassNo)                                       
                                                    CurrStage   = Xlate('RDS', RDSNo, 'CURR_STAGE', 'X')

                                                    Begin Case
                                                            
                                                        Case CurrStage _EQC 'PRE'
                                                            Action = 'LOAD'
                                                            
                                                            // Check if both the PRE and LOAD stages are ready to sign
                                                            // If a Pre Clean is required, then the OpenInsight UI must be used for now until support for cleans operations
                                                            // is added to the barcode web application.
                                                            LotNo          = RDSRec<RDS_LOT_NUM$>
                                                            PreCleanReq    = False$
                                                            CleanInspKeyID = Xlate('RDS', RDSNo, 'PRE_CI_NO', 'X')
*                                                            If CleanInspKeyID NE '' then PreCleanReq = Xlate('CLEAN_INSP', CleanInspKeyID, 'SPEC_CLEAN_REQ', 'X')
                                                            PSN            = RDSRec<RDS_PROD_SPEC_ID$>
                                                            PrePRSStageKey = PSN:'*PRE'
                                                            PreCleanReq    = Xlate('PRS_STAGE', PrePRSStageKey, 'CLEAN_SIG_REQ', 'X')
                                                            
                                                            If ( (PreCleanReq EQ False$) or (PreCleanReq EQ '') ) then                                                                
                                                                // The first run on a reactor requires that RDS Layer parameters be entered manually. This is when Prove-in
                                                                // occurs.
                                                                RunOrderNo = Xlate('RDS', RDSNo, 'RUN_ORDER_NUM', 'X')
                                                                If RunOrderNo GT 1 then               
                                                                    // Wafer is eligible to have recipe parameters automatically applied from the previous cassette.
                                                                    // Only copy RDS layer parameters if they do not already exist. This way we do not overwrite
                                                                    // manually entered parameters.
                                                                    LSParmsComp     = Xlate('RDS', RDSNo, 'LS_PARMS_COMP', 'X')
                                                                    FirstParmsComp  = LSParmsComp<1,1>
                                                                    If (FirstParmsComp EQ False$) then RDS_Services('CopyRDSLayerParameters', RDSNo)
                                                                    // Supplier lot verification
                                                                    ScannedSuppLot = ScansRow<SCANS.SUPPLIER_LOT$>
                                                                    RDSSuppLot     = RDSRec<RDS_LOT_NUM$>
                                                                    If (ScannedSuppLot NE '') then
                                                                        If (ScannedSuppLot _EQC RDSSuppLot) then
                                                                            WaferCountAckReq = Xlate('RDS', RDSNo, 'WAFER_COUNT_ACK_REQ', 'X')
                                                                            If WaferCountAckReq EQ False$ then                                                                    
                                                                                PreStageReady  = ''
                                                                                LoadStageReady = ''
                                                                                PreStageReady  = QA_Services('PreEpiSignatureReady', RDSNo, Username, WaferQty, Reactor)
                                                                                If PreStageReady EQ True$ then
																					LoadStageReady  = QA_Services('LoadSignatureReady', RDSNo, Username, WaferQty, LLSide, True$, Reactor)
																					ROTRBlock       = XLATE('REACTOR', Reactor, 47, 'X')
																					ROTRBlockReason = XLATE('REACTOR', Reactor, REACTOR_ROTR_STATUS_REASON$, 'X')
																					ROTREnabled     = XLATE('REACTOR', Reactor, 49, 'X')
																					
																					If ROTRBlock NE 'P' AND ROTREnabled EQ true$ then
																						ScansRow<SCANS.OVERRIDE_REQD$>   = 1
																						ScansRow<SCANS.OVERRIDE_REASON$> = ROTRBlockReason
																						ScansRow<SCANS.OVERRIDE_TYPE$>   = 'ROTR'
																						Error_Services('Clear')
																						Scan_Services('AddNotAcceptableReason', "ROTR Load Block Enabled")
																						
																					end else
																						ScansRow<SCANS.OVERRIDE_REQD$>   = 0
																						ScansRow<SCANS.OVERRIDE_REASON$> = ''
																						ScansRow<SCANS.OVERRIDE_TYPE$>   = ''
																					end
                                                                                end
                                                                            end else
                                                                                Scan_Services('AddNotAcceptableReason', 'The cassette wafer count must be verified against the scheduled wafer count to proceed.')
                                                                            end
                                                                        end else
                                                                            ScansRow<SCANS.SUPPLIER_LOT$> = ''
                                                                            Error_Services('Add', '(':CassetteID:') Supplier lot mismatch.')
                                                                        end
                                                                    end else
                                                                        Scan_Services('AddNotAcceptableReason', 'Supplier lot scan required in order to complete a tool scan.')
                                                                    end
                                                                                                                                                        
                                                                end else
                                                                    Error_Services('Add', '(':CassetteID:') The first run must be completed using the OpenInsight user interface.')                                                                                                             
                                                                end                                               
                                                            end else
                                                                Error_Services('Add', '(':CassetteID:') A pre-clean is required for this RDS. The OpenInsight user interface must be used to proceed.')
                                                            end                                        
                                                        Case CurrStage _EQC 'LOAD'
                                                            Action = 'LOAD'
                                                            ScannedSuppLot = ScansRow<SCANS.SUPPLIER_LOT$>
                                                            RDSSuppLot     = RDSRec<RDS_LOT_NUM$>
                                                            If (ScannedSuppLot NE '') then
                                                                If (ScannedSuppLot _EQC RDSSuppLot) then
                                                                    // Scheduled tool verification (only supports reactors at the moment)
                                                                    SchedTool = Xlate('RDS', RDSNo, 'SCHED_REACTOR', 'X')
                                                                    SchedTool = 'R':SchedTool
                                                                    ScanTool  = ScansRow<SCANS.TOOL_ID$>
                                                                    If SchedTool EQ ScanTool then
                                                                    	// Check if LOAD stage is ready to sign
                                                                        RDSLayerAckReq  = Xlate('RDS', RDSNo, 'RDS_LAYER_ACK_REQ', 'X')
                                                                        If RDSLayerAckReq EQ False$ then
                                                                            If LWIInstAckReq EQ False$ then
                                                                                If LoadInstAckReq EQ False$ then
                                                                                    LoadStageReady = QA_Services('LoadSignatureReady', RDSNo, Username, WaferQty, LLSide)
                                                                                    
                                                                                    ROTRBlock = XLATE('REACTOR', Reactor, 47, 'X')
                                                                                    ROTRBlockReason = XLATE('REACTOR', Reactor, REACTOR_ROTR_STATUS_REASON$, 'X')
                                                                                    ROTREnabled = XLATE('REACTOR', Reactor, 49, 'X')
                                                                                    
                                                                                        If LoadStageReady EQ False$ AND ROTRBlock NE 'P' AND ROTREnabled EQ true$ then
                                                                                        ScansRow<SCANS.OVERRIDE_REQD$>   = 1
                                                                                        ScansRow<SCANS.OVERRIDE_TYPE$>   = 'ROTR'
                                                                                        ScansRow<SCANS.OVERRIDE_REASON$> = ROTRBlockReason
                                                                                        Error_Services('Clear')
                                                                                        Scan_Services('AddNotAcceptableReason', "ROTR Load Block Enabled")
                                                                                        
                                                                                    end else
                                                                                        ScansRow<SCANS.OVERRIDE_REQD$>   = 0
                                                                                        ScansRow<SCANS.OVERRIDE_REASON$> = ''
                                                                                        ScansRow<SCANS.OVERRIDE_TYPE$>   = ''
                                                                                    end
                                                                                end else
                                                                                    Scan_Services('AddNotAcceptableReason', 'The LOAD stage engineering instructions must be acknowledged before the load operation can be signed.')
                                                                                end
                                                                            end else
                                                                                Scan_Services('AddNotAcceptableReason', 'The LWI stage engineering instructions must be acknowledged before the load operation can be signed.')
                                                                            end                                                                                                                                                                                                
                                                                        end else
                                                                            ErrorMessage = 'RDS layer parameters must be reviewed for accuracy and acknowledged before the load operation can be signed.'
                                                                            Scan_Services('AddNotAcceptableReason', ErrorMessage)
                                                                        end
                                                                    end else
                                                                        Error_Services('Add', 'Scanned tool ':ScanTool:' does not match the scheduled tool ':SchedTool:'. (':RDSNo:')')
                                                                    end
                                                                end else
                                                                    ScansRow<SCANS.SUPPLIER_LOT$> = ''
                                                                    Error_Services('Add', '(':CassetteID:') Supplier lot mismatch.')
                                                                end
                                                            end else
                                                                Scan_Services('AddNotAcceptableReason', 'Supplier lot scan required in order to complete a tool scan.')
                                                            end
                                                            
                                                        Case CurrStage _EQC 'UNLOAD'
                                                            Action = 'UNLOAD'
                                                            // Check if UNLOAD stage is ready to sign
                                                            If ( (LWIInstAckReq EQ False$) or (LWIInstAckReq EQ '') ) then
            
                                                                If ( (UnloadInstAckReq EQ False$) or (UnloadInstAckReq EQ '') ) then
                                                                    If ( (PostInstAckReq EQ False$) or (PostInstAckReq EQ '') ) then
                                                                        UnloadStageReady = QA_Services('UnloadSignatureReady', RDSNo, Username, Reactor)
                                                                    end else
                                                                        ScanMsg = 'The POST stage engineering instructions must be acknowledged before the load operation can be signed.'
                                                                        Scan_Services('AddNotAcceptableReason', ScanMsg)                                                                  
                                                                    end
                                                                end else
                                                                    ScanMsg = 'The UNLOAD stage engineering instructions must be acknowledged before the load operation can be signed.'
                                                                    Scan_Services('AddNotAcceptableReason', ScanMsg)
                                                                end                                                  
                                                            end else
                                                                ScanMsg = 'The LWI stage engineering instructions must be acknowledged before the load operation can be signed.'
                                                                Scan_Services('AddNotAcceptableReason', ScanMsg)
                                                            end                                                
                                                        Case CurrStage _EQC 'LWIS'
                                                            Action = 'LWIS'
                                                            Error_Services('Add', '(':CassetteID:') The ':CurrStage:' is not currently supported by the barcode application. The OpenInsight user interface must be used in order to proceed.')
                                                        Case CurrStage _EQC 'LWII'
                                                            Action = 'LWII'
                                                            Error_Services('Add', '(':CassetteID:') The ':CurrStage:' is not currently supported by the barcode application. The OpenInsight user interface must be used in order to proceed.')
                                                        Case CurrStage _EQC 'FQA'
                                                            Action = 'FQA'
                                                            Error_Services('Add', '(':CassetteID:') The ':CurrStage:' is not currently supported by the barcode application. The OpenInsight user interface must be used in order to proceed.')
                                                        Case CurrStage _EQC 'COMP'
                                                            Action = 'COMP'
                                                            Error_Services('Add', '(':CassetteID:") Cassette has already been FQA'd.")
                                                        Case Otherwise$
                                                    End Case
                                                end else
                                                    Scan_Services('AddNotAcceptableReason', 'EmployeeID required to complete a tool scan.')                      
                                                end
                                            end else
                                                Error_Services('Add', '(':CassetteID:') Process Error: cassette is on Hold and may not be signed off.')
                                            end                                           
                                        end
                                    end else
                                        Error_Services('Add', '(':CassetteID:') WMI/WMO cassette are not currently supported for tool scans. The OpenInsight user interface must be used in order to proceed.')
                                    end
                                end else
                                    // Currently we are only supporting loading one cassette onto a tool at a time.
                                    Error_Services('Add', '(':CassetteID:') Only one cassette can be loaded onto a tool at a time.')
                                end
                            end else
                                Scan_Services('AddNotAcceptableReason', 'A cassette must be scanned in order to complete a tool scan.')
                            end

                        Case Otherwise$                       
                            Scan_Services('AddNotAcceptableReason', 'A location ID or tool ID must be scanned to proceed.')
                    End Case
                end
                // If the the scan data is valid, update the scan log before returning to the calling process.
                
                If Error_Services('NoError') then
                    If Scan_Services('NoNotAcceptableReason') then              
                        ScansRow<SCANS.ACCEPTABLE$>             = True$
                        ScansRow<SCANS.NOT_ACCEPTABLE_REASON$>  = ''
                    end else
                        ScansRow<SCANS.ACCEPTABLE$>             = False$
                        ScansRow<SCANS.NOT_ACCEPTABLE_REASON$>  = Scan_Services('GetNotAcceptableReason')
                    end                    
                end else                    
                    ScansRow<SCANS.ACCEPTABLE$>             = False$
                    ScansRow<SCANS.NOT_ACCEPTABLE_REASON$>  = Error_Services('GetMessage')                           
                end
                If ScanData NE '' then                   
                    ScansRow<SCANS.SCANNED_DATES$, -1>      = Date()
                    ScansRow<SCANS.SCANNED_TIMES$, -1>      = Time()
                    ScansRow<SCANS.SCANNED_DATA$, -1>       = ScanData
                end
                ScansRow<SCANS.ACTION$> = Action
                // Save error message if present as Database_Services will clear any errors.
                ErrorMessage = ''
                If Error_Services('HasError') then
                    ErrorMessage = Error_Services('GetMessage')
                end
                Database_Services('WriteDataRow', 'SCANS', ScanID, ScansRow, True$, False$, True$)
                // Restore pre-existing error message if present.
                If ErrorMessage NE '' then Error_Services('Add', ErrorMessage)                            
            end
        end else
            Error_Services('Add', 'Unable to parse the JSON scan data in the ':Service:' service.')
        end
        SRP_JSON(hScanJSON, 'Release')
    end else
        Error_Services('Add', 'ScanID or ScanJSON argument was missing in the ' : Service : ' service.')
    end    

end service


//----------------------------------------------------------------------------------------------------------------------
// AcceptScan
//
//----------------------------------------------------------------------------------------------------------------------
Service AcceptScan(ScanID, ScanJSON)

    If ( (ScanID NE '') and (ScanJSON NE '') ) then
        hBody         = ''
        ParseResponse = SRP_JSON(hBody, 'PARSE', ScanJSON)
        If ParseResponse EQ '' then
            AcceptedStatus  = SRP_JSON(hBody, 'GetValue', 'accepted.status')
            If AcceptedStatus NE '' then
                ScansRow                    = Scan_Services('GetScansRow', ScanID, False$)
                ScansRow<SCANS.ACCEPTED$>   = AcceptedStatus
                Result                      = ''
                If AcceptedStatus EQ True$ then
                    // Scan is acceptable and has been accepted on the web app. Sign stage now if tool scan.
                    AcceptedDate    = Date()
                    AcceptedTime    = Time()
                    LogDate         = OCONV( AcceptedDate, 'D2/' )
                    LogTime         = OCONV( AcceptedTime, 'MTS' )                                                                         
                    LogDTM 	        = LogDate:' ':LogTime
                    ScansRow<SCANS.ACCEPTED_DATE$>  = AcceptedDate
                    ScansRow<SCANS.ACCEPTED_TIME$>  = AcceptedTime
                    CassetteIDs = ScansRow<SCANS.CASSETTE_IDS$>
                    ScanType    = ScansRow<SCANS.SCAN_TYPE$>   
                    Begin Case
                        Case ScanType _EQC 'RESET'
                            Scan_Services('ResetDevData', CassetteIDs)
                            If Error_Services('NoError') then Result = 'Reset scan complete'
                              
                        Case ScanType _EQC 'TENCOR_LOAD'
                             Tencor= ScansRow<SCANS.TOOL_ID$>
                              RDSNo=  ScansRow<SCANS.CASSETTE_IDS$>
                            SendStatus = Keyboard_Sim_Services('SendTencor', Tencor, RDSNo)
                            If Error_Services('NoError') AND SendStatus EQ 'Success' then
                                Result = 'Tencor Data Sent Successfully'
                            end else
                                Error_Services('Add', SendStatus)
                            end
                            
                        Case ScanType _EQC 'LOCATION'
                            
                            //DPC - 2/10/20 - need to check for existence of 1K*PTI scan before location scan is allowed
                            LocID        = ScansRow<SCANS.LOCATION_ID$>
                            ErrorCassIDs = '' 
                            WONos        = ''
                            CassNos      = ''
                            For each CassetteID in CassetteIDs using @VM setting vPos
                                Begin Case
                                    Case Count(CassetteID, '.') EQ 0
                                        // RDS Format
                                        IsEpiPro = RDS_Services('IsEpiPro', CassetteID)
                                        If IsEpiPro EQ False$ then
                                            // Non-EpiPro RDS
                                            WONo        = Xlate('RDS', CassetteID, 'WO', 'X')
                                            CassNo      = Xlate('RDS', CassetteID, 'CASS_NO', 'X')
                                            WOMatKey    = WONo:'*':CassNo
                                            WOMatLocs   = Xlate('WO_MAT', WOMatKey, WO_MAT_INV_LOCATION$, 'X')
                                            If Index(WOMatLocs,'PTI',1) then
                                                WONos<0, -1>    = WONo
                                                CassNos<0, -1>  = CassNo                     
                                            end else
                                                // Cassette has not yet been through passthrough (PTI), so it is not eligible for location scanning.
                                                // Add it to the invalid cassette list (error list).                                    
                                                ErrorCassIDs<0, -1> = CassetteID                                                     
                                            end                                            
                                        end else
                                            // EpiPro RDS                                           
                                            OutCassNos = Xlate('RDS', CassetteID, 'OUT_CASS_NO', 'X')
                                            OutCassNos = SRP_Array('Clean', OutCassNos, 'TrimAndMakeUnique', @VM)
                                            For each OutCassNo in OutCassNos using @VM setting oPos
                                                WONo        = Xlate('RDS', CassetteID, 'WO', 'X')
                                                CassNo      = OutCassNo
                                                WOMatKey    = WONo:'*':CassNo
                                                WOMatLocs   = Xlate('WO_MAT', WOMatKey, WO_MAT_INV_LOCATION$, 'X')
                                                If Index(WOMatLocs,'PTI',1) then
                                                    WONos<0, -1>    = WONo
                                                    CassNos<0, -1>  = CassNo                     
                                                end else
                                                    // Cassette has not yet been through passthrough (PTI), so it is not eligible for location scanning.
                                                    // Add it to the invalid cassette list (error list).                                    
                                                    ErrorCassIDs<0, -1> = CassetteID                                                   
                                                end                  
                                            Next OutCassNo
                                        end     
                                    Case DCount(CassetteID, '.') EQ 3
                                        // WM_IN / WM_OUT Format
                                        WONo   = Field(CassetteID, '.', 1, 1)
                                        CassNo = Field(CassetteID, '.', 3, 1)
                                        WOMatKey    = WONo:'*':CassNo
                                        WOMatLocs   = Xlate('WO_MAT', WOMatKey, WO_MAT_INV_LOCATION$, 'X')
                                        If Index(WOMatLocs,'PTI',1) then
                                            WONos<0, -1>    = WONo
                                            CassNos<0, -1>  = CassNo                     
                                        end else
                                            // Cassette has not yet been through passthrough (PTI), so it is not eligible for location scanning.
                                            // Add it to the invalid cassette list (error list).                                    
                                            ErrorCassIDs<0, -1> = CassetteID                                               
                                        end         
                                    Case Otherwise$
                                        // Unsupported format
                                        Null
                                End Case
                                                                  
                            Next CassetteID
                            
                            For each CassetteID in ErrorCassIDs using @VM
                                // Remove erroneous cassettes from the valid cassette list
                                Locate CassetteID in CassetteIDs using @VM setting vPos then
                                    CassetteIDs = Delete(CassetteIDS, 0, vPos, 0)
                                end
                            Next CassetteID

                            If ( (WONos NE '') and (CassNos NE '') ) then
                                // Move the cassettes (i.e. log their movement in the material log)
                                LogFile = 'WO_MAT'               
                                LogDTM  = LogDate:' ':LogTime    
                                Action  = 'PLACE'                
                                WhCd    = Field(LocID, '.', 1, 1) 
                                LocCd   = Field(LocID, '.', 2, 1)
                                UserID  = ScansRow<SCANS.EMPLOYEE_ID$>
                                Tags    = ''
                                ToolID  = ''
                                ScanEntry = 1
                                
                                WOMLParms  = LogFile:@RM
                                WOMLParms := LogDTM:@RM
                                WOMLParms := Action:@RM
                                WOMLParms := WhCd:@RM
                                WOMLParms := LocCd:@RM
                                WOMLParms := WONos:@RM
                                WOMLParms := CassNos:@RM
                                WOMLParms := UserID:@RM
                                WOMLParms := Tags:@RM
                                WOMLParms := ToolID:@RM
                                WOMLParms := ScanEntry 
                                                    
                                obj_WO_Mat_Log('Create',WOMLParms)
                                errCode = ''
                            end
                                               
                            IF Get_Status(errCode) THEN
                                ErrorMsg = 'Error calling obj_WO_Mat_Log("Create"). Error code: ':errCode
                                Error_Services('Add', ErrorMsg)
                            END else
                                NumCass = DCount(CassetteIDs, @VM)
                                If NumCass EQ 1 then
                                    CassetteID = CassetteIDs<0, 1>
                                    Result = '(':CassetteID:') Location scan complete. '
                                end else     
                                    Result = '(Multi) Location scan complete. '                                                                     
                                end
                                If ErrorCassIDs NE '' then
                                    Convert @VM to ',' in ErrorCassIDs
                                    Result := 'Cassette(s) ':ErrorCassIDs:' failed due to missing PTI.'
                                end
                            end
                            
                             
                        Case ScanType _EQC 'TOOL'
                            // Check if cassette field is populated. If so, then check the cassette's current status to
                            // determine what the next action will be (i.e. Pre-Epi+Load, Unload, etc.)
                            CassetteID = CassetteIDs<0, 1>                                  
                            RDSNo      = CassetteID
                            RDSRec     = Database_Services('ReadDataRow', 'RDS', RDSNo)
                            ToolID     = ScansRow<SCANS.TOOL_ID$>
                            Reactor    = ToolID[-1, 'BR']
                            Username   = ScansRow<SCANS.EMPLOYEE_ID$>
                            LLSide     = ScansRow<SCANS.LOAD_LOCK$>
                            WaferQty   = RDSRec<RDS_CASS_WAFER_QTY$>
                            // Check signature fields to determine where the lot is in the RDS process. The service should
                            // use the signatures in the RDS table, not the WO_MAT table, so that we can support both
                            // EpiPro and non-EpiPro lots.
                            CurrStage  = Xlate('RDS', RDSNo, 'CURR_STAGE', 'X')
                            CurrDTM    = Datetime()
                            LogData    = ''
                            LogData<1> = OConv(CurrDTM, 'DT')
                            LogData<2> = RDSNo
                            LogData<3> = Username
                            LogData<4> = CurrStage
                            LogData<5> = ScanID
                            Begin Case                          
                                Case CurrStage _EQC 'PRE'
                                    Action = 'LOAD'
                                    // Check if both the PRE and LOAD stages are ready to sign
                                    PreStageSigned  = False$
                                    LoadStageSigned = False$
                                    PreStageSigned  = QA_Services('SignPreEpiStage', RDSNo, Username, WaferQty, Reactor, 1)
                                    If PreStageSigned EQ True$ then
                                        LoadStageSigned = QA_Services('SignLoadStage', RDSNo, USername, WaferQty, LLSide, 1)
                                    end
                                    If ( (PreStageSigned EQ True$) and (LoadStageSigned EQ True$) ) then
                                        Result = '(':CassetteID:') Load stage signed.'                                           
                                    end
                                    LogData<6> = Action
                                    LogData<7> = Result
                                    Logging_Services('AppendLog', objLog, LogData, @RM, @FM)
                                Case CurrStage _EQC 'LOAD'
                                    Action          = 'LOAD'
                                    // Check if LOAD stage is ready to sign
                                    LoadStageSigned = QA_Services('SignLoadStage', RDSNo, Username, WaferQty, LLSide, 1)
                                    If LoadStageSigned EQ True$ then
                                        Result = '(':CassetteID:') Load stage signed.'
                                    end
                                    LogData<6> = Action
                                    LogData<7> = Result
                                    Logging_Services('AppendLog', objLog, LogData, @RM, @FM)
                                Case CurrStage _EQC 'UNLOAD'
                                    Action = 'UNLOAD'
                                    // Check if UNLOAD stage is ready to sign
                                    UnloadStageSigned = QA_Services('SignUnloadStage', RDSNo, Username, 1)
                                    If UnloadStageSigned EQ True$ then
                                        Result = '(':CassetteID:') Unload stage signed.'
                                    end
                                    LogData<6> = Action
                                    LogData<7> = Result
                                    Logging_Services('AppendLog', objLog, LogData, @RM, @FM)
                                Case CurrStage _EQC 'LWIS'
                                    Action = 'LWIS'
                                    Error_Services('Set', '(':CassetteID:') The ':CurrStage:' is not currently supported by the barcode application.')
                                Case CurrStage _EQC 'LWII'
                                    Action = 'LWII'
                                    Error_Services('Set', '(':CassetteID:') The ':CurrStage:' is not currently supported by the barcode application.')
                                Case CurrStage _EQC 'FQA'
                                    Action = 'FQA'
                                    Error_Services('Set', '(':CassetteID:') The ':CurrStage:' is not currently supported by the barcode application.')
                                Case CurrStage _EQC 'COMP'
                                    Action = 'COMP'
                                    Error_Services('Set', '(':CassetteID:") Cassette has already been FQA'd.")
                                Case Otherwise$
                            End Case            
                    End Case
                end
                
                If Error_Services('NoError') then
                    ScansRow<SCANS.NOT_ACCEPTABLE_REASON$>  = 'This scan has already been accepted.'
                end else 
                    ScansRow<SCANS.NOT_ACCEPTABLE_REASON$>  = Error_Services('GetMessage')                            
                end
                ScansRow<SCANS.ACCEPTABLE$> = False$
                If Assigned(Result) then ScansRow<SCANS.RESULT$> = Result
                // Save error message if present as Database_Services will clear any errors.
                ErrorMessage = ''
                If Error_Services('HasError') then
                    ErrorMessage = Error_Services('GetMessage')
                end
                Scan_Services('SetScansRow', ScanID, ScansRow)
                // Restore pre-existing error message if present.
                If ErrorMessage NE '' then Error_Services('Add', ErrorMessage)    
            end else
                Error_Services('Add', 'The accepted.status field is missing from the JSON object in the ':Service:' service.')
            end
            SRP_JSON(hBody, 'Release')
        end else
            Error_Services('Add', 'Unable to parse the JSON scan resource in the ':Service:' service.')
        end
    end else
        Error_Services('Add', 'ScanID or ScanJSON argument was missing in the ' : Service : ' service.')
    end
    
end service

//----------------------------------------------------------------------------------------------------------------------
// ConvertMVScanToJSON
//
// Converts a MultiValue formatted SCANS row into a serialized JSON object and returns the result. If the mvScan
// argument is empty, the service will attempt to get it from the SCANS table. If the itemURL argument is not empty,
// HAL+JSON properties will be added to the JSON object.
//----------------------------------------------------------------------------------------------------------------------
Service ConvertMVScanToJSON(ScanID, mvScan, itemURL)

    jsonScan    = ''

    If ScanID NE '' then

        If mvScan EQ '' then mvScan = Database_Services('ReadDataRow', 'SCANS', ScanID)
        If Error_Services('NoError') then
            @DICT       = Database_Services('GetTableHandle', 'DICT.SCANS')
            @ID         = ScanID
            @RECORD     = mvScan
            objJSONScan = ''
            If SRP_JSON(objJSONScan, 'New', 'Object') then
                // Scan object.
                If SRP_JSON(objScan, 'New', 'Object') then
                    SRP_JSON(objScan, 'SetValue', 'ID', @ID, 'String')
                    SRP_JSON(objScan, 'SetValue', 'type', {SCAN_TYPE}, 'String')
                    SRP_JSON(objScan, 'SetValue', 'action', {ACTION}, 'String')
                    SRP_JSON(objScan, 'SetValue', 'acceptable', {ACCEPTABLE}, 'Boolean')
                    SRP_JSON(objScan, 'SetValue', 'notAcceptableReason', {NOT_ACCEPTABLE_REASON}, 'String')
                    SRP_JSON(objScan, 'SetValue', 'result', {RESULT}, 'String')
                    If SRP_JSON(arrayScanLog, 'New', 'Array') then
                        If {SCANNED_DATES} NE '' then
                            ScannedDates    = {SCANNED_DATES}
                            For Each ScannedDate in scannedDates using @VM setting vPos
                                objScanSession = ''
                                If SRP_JSON(objScanSession, 'New', 'Object') then
                                    SRP_JSON(objScanSession, 'SetValue', 'date', Oconv({SCANNED_DATES}<0, vPos>, 'D4/'), 'String')
                                    SRP_JSON(objScanSession, 'SetValue', 'time', Oconv({SCANNED_TIMES}<0, vPos>, 'MTH'), 'String')
                                    SRP_JSON(objScanSession, 'SetValue', 'data', {SCANNED_DATA}<0, vPos>, 'String')
                                    SRP_JSON(arrayScanLog, 'Add', objScanSession)
                                    SRP_JSON(objScanSession, 'Release')
                                end
                            Next ScannedDate
                        end
                        SRP_JSON(objScan, 'Set', 'scanLog', arrayScanLog)
                        SRP_JSON(arrayScanLog, 'Release')
                    end

                    SRP_JSON(objJSONScan, 'Set', 'scan', objScan)
                    SRP_JSON(objScan, 'Release')
                end
                // Supplier Lot object.
                objSupplierLot = ''
                If SRP_JSON(objSupplierLot, 'New', 'Object') then
                    SRP_JSON(objSupplierLot, 'SetValue', 'ID', {SUPPLIER_LOT}, 'String')
                    SRP_JSON(objJSONScan, 'Set', 'supplierLot', objSupplierLot)
                    SRP_JSON(objSupplierLot, 'Release')
                end
                // Created object.
                objCreated = ''
                If SRP_JSON(objCreated, 'New', 'Object') then
                    SRP_JSON(objCreated, 'SetValue', 'date', Oconv({CREATED_DATE}, 'D4/'), 'String')
                    SRP_JSON(objCreated, 'SetValue', 'time', Oconv({CREATED_TIME}, 'MTH'), 'String')
                    SRP_JSON(objJSONScan, 'Set', 'created', objCreated)
                    SRP_JSON(objCreated, 'Release')
                end
                // Employee object
                objEmployee = ''
                If SRP_JSON(objEmployee, 'New', 'Object') then
                    
                    SRP_JSON(objEmployee, 'SetValue', 'ID', {EMPLOYEE_ID}, 'String')
                    SRP_JSON(objEmployee, 'SetValue', 'name', {EMPLOYEE_NAME}, 'String')
                    SRP_JSON(objEmployee, 'SetValue', 'authorized', {EMPLOYEE_AUTHORIZED}, 'Boolean')
                    SRP_JSON(objEmployee, 'SetValue', 'notAuthorizedReason', {EMPLOYEE_NOT_AUTHORIZED_REASON}, 'String')
                    SRP_JSON(objEmployee, 'SetValue', 'authenticated'       , @Record<SCANS.AUTHENTICATED$>            , 'Boolean');//JRO Change
                    SRP_JSON(objJSONScan, 'Set', 'employee', objEmployee)
                    SRP_JSON(objEmployee, 'Release')
                    
                end
                // Cassettes object.
                arrayCassetteIDs = ''
                If SRP_JSON(arrayCassetteIDs, 'New', 'Array') then
                    If {CASSETTE_IDS} NE '' then
                        CassetteIDs = {CASSETTE_IDS}
                        For Each CassetteID in CassetteIDs using @VM
                            SRP_JSON(arrayCassetteIDs, 'AddValue', CassetteID, 'String')
                        Next CassetteID
                    end
                    SRP_JSON(objJSONScan, 'Set', 'cassetteIDs', arrayCassetteIDs)
                    SRP_JSON(arrayCassetteIDs, 'Release')
                end
                // Location object.
                objLocation = ''
                If SRP_JSON(objLocation, 'New', 'Object') then
                    SRP_JSON(objLocation, 'SetValue', 'ID', {LOCATION_ID}, 'String')
                    SRP_JSON(objLocation, 'SetValue', 'name', {LOCATION_NAME}, 'String')
                    SRP_JSON(objJSONScan, 'Set', 'location', objLocation)
                    SRP_JSON(objLocation, 'Release')
                end
                // Tool object.
                objTool = ''
                If SRP_JSON(objTool, 'New', 'Object') then
                    SRP_JSON(objTool, 'SetValue', 'ID', {TOOL_ID}, 'String')
                    SRP_JSON(objTool, 'SetValue', 'name', {TOOL_NAME}, 'String')
                    SRP_JSON(objTool, 'SetValue', 'loadLock', {LOAD_LOCK}, 'String')
                    SRP_JSON(objJSONScan, 'Set', 'tool', objTool)
                    SRP_JSON(objTool, 'Release')
                end
                // Accepted object.
                objAccepted = ''
                If SRP_JSON(objAccepted, 'New', 'Object') then
                    SRP_JSON(objAccepted, 'SetValue', 'status', {ACCEPTED}, 'Boolean')
                    SRP_JSON(objAccepted, 'SetValue', 'date', Oconv({ACCEPTED_DATE}, 'D4/'), 'String')
                    SRP_JSON(objAccepted, 'SetValue', 'time', Oconv({ACCEPTED_TIME}, 'MTH'), 'String')
                    SRP_JSON(objEmployee, 'SetValue', 'notAcceptedReason', {NOT_ACCEPTABLE_REASON}, 'String')
                    SRP_JSON(objJSONScan, 'Set', 'accepted', objAccepted)
                    SRP_JSON(objAccepted, 'Release')
                end
                // Wafer Count object.
                objWaferCount = ''
                If SRP_JSON(objWaferCount, 'New', 'Object') then
                    SRP_JSON(objWaferCount, 'SetValue', 'scheduler', {SCHEDULER_WAFER_COUNT}, 'Number')
                    SRP_JSON(objWaferCount, 'SetValue', 'confirmed', {WAFER_COUNT_CONFIRMED}, 'Boolean')
                    SRP_JSON(objJSONScan, 'Set', 'waferCount', objWaferCount)
                    SRP_JSON(objWaferCount, 'Release')
                end
                // Transfer object.
                objTransfer = ''
                If SRP_JSON(objTransfer, 'New', 'Object') then
                    // Transfer Tool sub-object.
                    objTool = ''
                    If SRP_JSON(objTool, 'New', 'Object') then
                        SRP_JSON(objTool, 'SetValue', 'ID', {TRANSFER_TOOL_ID}, 'String')
                        SRP_JSON(objTool, 'SetValue', 'name', {TRANSFER_TOOL_NAME}, 'String')
                        SRP_JSON(objTransfer, 'Set', 'tool', objTool)
                        SRP_JSON(objTool, 'Release')
                    end
                    SRP_JSON(objTransfer, 'SetValue', 'boatID', {BOAT_ID}, 'String')
                    SRP_JSON(objTransfer, 'SetValue', 'plNumber', {PL_NUMBER}, 'String')
                    SRP_JSON(objTransfer, 'SetValue', 'unloadplConfirmed', {UNLOAD_PL_CONFIRMED}, 'Boolean')
                    SRP_JSON(objJSONScan, 'Set', 'transfer', objTransfer)
                    SRP_JSON(objTransfer, 'Release')
                end
                SRP_JSON(objJSONScan, 'SetValue', 'lastModified', {LAST_MODIFIED}, 'String')
                
                If {SCAN_TYPE} EQ 'TOOL' then
                    CassIDs   = {CASSETTE_IDS}
                    RDSNo     = CassIDs<0, 1>
                    CurrStage = Xlate('RDS', RDSNo, 'CURR_STAGE', 'X')
                    If ( ({EMPLOYEE_ID} NE '') and ({TOOL_ID} NE '') and ({SUPPLIER_LOT} NE '') and ({CASSETTE_IDS} NE '') and ( (CurrStage EQ 'LOAD') or (CurrStage EQ 'PRE') ) ) |
                    or ( ({EMPLOYEE_ID} NE '') and ({TOOL_ID} NE '') and ({CASSETTE_IDS} NE '') and (CurrStage EQ 'UNLOAD') ) then
                        // RDS object
                        objRDS = ''
                        If SRP_JSON(objRDS, 'New', 'Object') then

                            RDSRec           = Database_Services('ReadDataRow', 'RDS', RDSNo)
                                            
                            SupplInstAckReq         = Xlate('RDS', RDSNo, 'SUPPL_ACK_REQ'         , 'X')                    
                            RDSLayerInstAckReq      = Xlate('RDS', RDSNo, 'RDS_LAYER_ACK_REQ'     , 'X')   
                            RDSLayerIDs             = Xlate('RDS', RDSNo, 'RDS_LS_ID'             , 'X')
                            PreInstAckReq           = Xlate('RDS', RDSNo, 'PRE_INST_ACK_REQ'      , 'X')
                            PreInst                 = Xlate('RDS', RDSNo, 'PRE_INST'              , 'X')
                            FwiInstAckReq           = Xlate('RDS', RDSNo, 'FWI_INST_ACK_REQ'      , 'X')
                            FwiInst                 = Xlate('RDS', RDSNo, 'FWI_INST'              , 'X')
                            LwiInstAckReq           = Xlate('RDS', RDSNo, 'LWI_INST_ACK_REQ'      , 'X')
                            LwiInst                 = Xlate('RDS', RDSNo, 'LWI_INST'              , 'X')
                            QAInstAckReq            = Xlate('RDS', RDSNo, 'QA_INST_ACK_REQ'       , 'X')
                            QAInst                  = Xlate('RDS', RDSNo, 'QA_INST'               , 'X')
                            LoadInstAckReq          = Xlate('RDS', RDSNo, 'LOAD_INST_ACK_REQ'     , 'X')
                            LoadInst                = Xlate('RDS', RDSNo, 'LOAD_INST'             , 'X')
                            UnloadInstAckReq        = Xlate('RDS', RDSNo, 'UNLOAD_INST_ACK_REQ'   , 'X')
                            UnloadInst              = Xlate('RDS', RDSNo, 'UNLOAD_INST'           , 'X')
                            PostInstAckReq     = Xlate('RDS', RDSNo, 'POST_INST_ACK_REQ'     , 'X')
                            PostInst           = Xlate('RDS', RDSNo, 'POST_INST'             , 'X')
                            WaferCountInstAckReq    = Xlate('RDS', RDSNo, 'WAFER_COUNT_ACK_REQ'   , 'X')                    
                            WaferCountInst          = 'Please verify the wafer quantity in the cassette matches the ' |
                            : 'scheduled wafer quantity, ':{SCHEDULER_WAFER_COUNT}:'.'
                            
                            SRP_JSON(objRDS, 'SetValue', 'supplInstAckReq'      , SupplInstAckReq               , 'Boolean')
                            SRP_JSON(objRDS, 'SetValue', 'supplInstAck'         , RDSRec<RDS_SUPPL_ACK$>        , 'Boolean')
                            SRP_JSON(objRDS, 'SetValue', 'supplInst'            , RDSRec<RDS_SUPPL_INST$>       , 'String')                    
                            SRP_JSON(objRDS, 'SetValue', 'preInstAckReq'        , PreInstAckReq                 , 'Boolean')
                            SRP_JSON(objRDS, 'SetValue', 'preInstAck'           , RDSRec<RDS_PRE_INST_ACK$>     , 'Boolean')
                            SRP_JSON(objRDS, 'SetValue', 'preInst'              , PreInst                       , 'String')
                            SRP_JSON(objRDS, 'SetValue', 'fwiInstAckReq'        , FwiInstAckReq                 , 'Boolean')
                            SRP_JSON(objRDS, 'SetValue', 'fwiInstAck'           , RDSRec<RDS_FWI_INST_ACK$>     , 'Boolean')
                            SRP_JSON(objRDS, 'SetValue', 'fwiInst'              , FwiInst                       , 'String')
                            SRP_JSON(objRDS, 'SetValue', 'lwiInstAckReq'        , LwiInstAckReq                 , 'Boolean')
                            SRP_JSON(objRDS, 'SetValue', 'lwiInstAck'           , RDSRec<RDS_LWI_INST_ACK$>     , 'Boolean')
                            SRP_JSON(objRDS, 'SetValue', 'lwiInst'              , LwiInst                       , 'String')
                            SRP_JSON(objRDS, 'SetValue', 'qaInstAckReq'         , QAInstAckReq                  , 'Boolean')
                            SRP_JSON(objRDS, 'SetValue', 'qaInstAck'            , RDSRec<RDS_QA_INST_ACK$>      , 'Boolean')
                            SRP_JSON(objRDS, 'SetValue', 'qaInst'               , QAInst                        , 'String')
                            SRP_JSON(objRDS, 'SetValue', 'loadInstAckReq'       , LoadInstAckReq                , 'Boolean')
                            SRP_JSON(objRDS, 'SetValue', 'loadInstAck'          , RDSRec<RDS_LOAD_INST_ACK$>    , 'Boolean')
                            SRP_JSON(objRDS, 'SetValue', 'loadInst'             , LoadInst                      , 'String')
                            SRP_JSON(objRDS, 'SetValue', 'unloadInstAckReq'     , UnloadInstAckReq              , 'Boolean')
                            SRP_JSON(objRDS, 'SetValue', 'unloadInstAck'        , RDSRec<RDS_UNLOAD_INST_ACK$>  , 'Boolean')
                            SRP_JSON(objRDS, 'SetValue', 'unloadInst'           , UnloadInst                    , 'String')
                            SRP_JSON(objRDS, 'SetValue', 'postInstAckReq'       , PostInstAckReq                , 'Boolean')
                            SRP_JSON(objRDS, 'SetValue', 'postInstAck'          , RDSRec<RDS_POST_INST_ACK$>    , 'Boolean')
                            SRP_JSON(objRDS, 'SetValue', 'postInst'             , PostInst                      , 'String')
                            SRP_JSON(objRDS, 'SetValue', 'waferCountInstAckReq' , WaferCountInstAckReq          , 'Boolean')
                            SRP_JSON(objRDS, 'SetValue', 'waferCountInstAck'    , RDSRec<RDS_WAFER_COUNT_ACK$>  , 'Boolean')
                            SRP_JSON(objRDS, 'SetValue', 'waferCountInst'       , WaferCountInst                , 'String')
                            SRP_JSON(objRDS, 'SetValue', 'rdsLayerInstAckReq'   , RDSLayerInstAckReq            , 'Boolean')
                            SRP_JSON(objRDS, 'SetValue', 'rdsLayerInstAck'      , RDSRec<RDS_RDS_LAYER_ACK$>    , 'Boolean')
                            
                            OverrideRequired = @Record<SCANS.OVERRIDE_REQD$>
                            OverrideType = @Record<SCANS.OVERRIDE_TYPE$>
                            OverrideReason = @Record<SCANS.OVERRIDE_REASON$>
                            SRP_JSON(objRDS, 'SetValue', 'overRideAckReq'       , OverrideRequired , 'Boolean')
	                        SRP_JSON(objRDS, 'SetValue', 'overRideType'     , OverrideType , 'string')
	                        SRP_JSON(objRDS, 'SetValue', 'overRideReason', @Record<SCANS.OVERRIDE_REASON$>, 'string')
                            If ((OverrideRequired EQ True$) and (OverrideType EQ 'ROTR')) then
                            	Swap " " with "" in OverrideReason
                            	OverrideComments = Xlate("ROTR_OVERRIDE_COMMENT_OPTIONS", OverrideReason, ROTR_OVERRIDE_COMMENT_OPTIONS_COMMENT_OPTIONS$, 'X')
                            	SRP_JSON(objRds, "SetValueArray", "overRideComments", OverrideComments, @VM)
                            	
								Reactor = {TOOL_ID}[2,999]
								rotrOverrideCount = Xlate('REACTOR', Reactor, REACTOR_ROTR_OVERRIDE_COUNT$, 'X')
								if rotrOverrideCount EQ '' then rotrOverrideCount = 0
								SRP_JSON(objRds, "SetValue", "rotrOverrideCount", rotrOverrideCount, 'Number')
                            end
                            arrayRDSLayers = ''
                            // Add RDSLayers (recipe parameters) array
                            If SRP_JSON(arrayRDSLayers, 'New', 'Array') then
                                For each RDSLayerID in RDSLayerIDs using @VM setting vPos
                                    arrayParameters = ''
                                    If SRP_JSON(arrayParameters, 'New', 'Array') then
                                        RDSLayerKey = RDSNo:'*':RDSLayerID                                       
                                        ColNames   = 'LS_ID,RECIPE_NO,RECIPE_NAME,DOPANT,EPI_DILUENT,EPI_TIME,'      |
                                                   : 'DILUENT_ADJ_PARAM,DOPANT_FLOW,HCL_FLOW,BAKE_TIME,EPI_H2_FLOW,' |
                                                   : 'TCS_FLOW,DCS_FLOW,AUX1,AUX2,F_OFFSET,S_OFFSET,R_OFFSET,'       |
                                                   : 'SUSC_ETCH,ETCH1,ETCH2,ETCH3,UL_TEMP'
                                        ParamList  = 'Layer ID,Recipe,Recipe Name,Dopant,Epi Diluent,Deposit,'       |
                                                   : 'Diluent,Dopant Flow,HCL Flow,Bake Time,H2 Flow,TCS Flow,'      |
                                                   : 'DCS Flow,Aux 1 Flow,Aux 2 flow,F Offset,S Offset,R Offset,'    |
                                                   : 'Susc Etch,Etch 1,Etch 2,Etch 3,Unload Temp'
                                        pos = ''
                                        For each Parameter in ParamList using ',' setting pos
                                            ColName    = Field(ColNames, ',', pos)
                                            ParamData  = Xlate('RDS_LAYER', RDSLayerKey, ColName, 'X')
                                            Conversion = Xlate('DICT.RDS_LAYER', ColName, DICT_CONV$, 'X')        
                                            If ParamData NE '' then
                                                If Conversion NE '' then ParamData = OConv(ParamData, Conversion)
                                                objParameter = ''
                                                If SRP_JSON(objParameter, 'New', 'Object') then
                                                    Success = SRP_JSON(objParameter, 'SetValue', 'Parameter', Parameter)
                                                    Success = SRP_JSON(objParameter, 'SetValue', 'Value', ParamData)
                                                    // Add parameter object to parameter array
                                                    Success = SRP_JSON(arrayParameters, 'Add', objParameter)
                                                    SRP_JSON(objParameter, 'Release')
                                                end                                                
                                            end                                                                            
                                        Next Value
                                    end
                                    // Add parameter array to RDS Layer array
                                    Success = SRP_JSON(arrayRDSLayers, 'Add', arrayParameters)
                                    SRP_JSON(arrayParameters, 'Release')
                                Next RDSLayerID
                                Success = SRP_JSON(objRDS, 'Set', 'rdsLayers', arrayRDSLayers)
                                SRP_JSON(arrayRDSLayers, 'Release')
                            end                                       
                            
                            SRP_JSON(objJSONScan, 'Set', 'rds', objRDS)
                            SRP_JSON(objRDS, 'Release')
                        end
                    end                    
                end

                If itemURL NE '' then
                    // The itemURL was passed in so add HAL+JSON properties.
                    objLinks = ''
                    // Create the _links property and then all link objects needed for this resource.
                    If SRP_JSON(objLinks, 'New', 'Object') then
                        // Create a self link.
                        objLink = ''
                        If SRP_JSON(objLink, 'New', 'Object') then
                            SRP_JSON(objLink, 'SetValue', 'href', ItemURL, 'String')
                            SRP_JSON(objLink, 'SetValue', 'title', 'Self', 'String')
                            SRP_JSON(objLinks, 'Set', 'self', objLink)
                            SRP_JSON(objLink, 'Release')
                        end
                        SRP_JSON(objJSONScan, 'Set', '_links', objLinks)
                        SRP_JSON(objLinks, 'Release')
                    end
                    objForm = ''
                    // Create the _form property to help UIs determine what to display.
                    If SRP_JSON(objForm, 'New', 'Object') then
                        arrayFields = ''
                        If SRP_JSON(arrayFields, 'New', 'Array') then
                            If {EMPLOYEE_ID} NE '' then
                                objField = ''
                                If SRP_JSON(objField, 'New', 'Object') then
                                    SRP_JSON(objField, 'SetValue', 'label', 'Employee', 'String')
                                    SRP_JSON(objField, 'SetValue', 'value', {EMPLOYEE_NAME}, 'String')
                                    SRP_JSON(arrayFields, 'Add', objField)
                                    SRP_JSON(objField, 'Release')
                                end
                            end
                            If {LOCATION_ID} NE '' then
                                If SRP_JSON(objField, 'New', 'Object') then
                                    SRP_JSON(objField, 'SetValue', 'label', 'Location', 'String')
                                    SRP_JSON(objField, 'SetValue', 'value', {LOCATION_NAME}, 'String')
                                    SRP_JSON(arrayFields, 'Add', objField)
                                    SRP_JSON(objField, 'Release')
                                end
                            end
                            If {TOOL_ID} NE '' then
                                If SRP_JSON(objField, 'New', 'Object') then
                                    SRP_JSON(objField, 'SetValue', 'label', 'Tool', 'String')
                                    ToolName    = {TOOL_NAME}
                                    If {LOAD_LOCK} NE '' then ToolName := ' ( ' : {LOAD_LOCK} : ' )'
                                    SRP_JSON(objField, 'SetValue', 'value', ToolName, 'String')
                                    SRP_JSON(arrayFields, 'Add', objField)
                                    SRP_JSON(objField, 'Release')
                                end
*                                If {LOAD_LOCK} NE '' then
*                                    If SRP_JSON(objField, 'New', 'Object') then
*                                        SRP_JSON(objField, 'SetValue', 'label', 'Load Lock', 'String')
*                                        SRP_JSON(objField, 'SetValue', 'value', {LOAD_LOCK}, 'String')
*                                        SRP_JSON(arrayFields, 'Add', objField)
*                                        SRP_JSON(objField, 'Release')
*                                    end
*                                end
                            end
                            If {TRANSFER_TOOL_ID} NE '' then
                                If SRP_JSON(objField, 'New', 'Object') then
                                    SRP_JSON(objField, 'SetValue', 'label', 'Transfer', 'String')
                                    SRP_JSON(objField, 'SetValue', 'value', {TRANSFER_TOOL_NAME}, 'String')
                                    SRP_JSON(arrayFields, 'Add', objField)
                                    SRP_JSON(objField, 'Release')
                                end
                            end
                            If {CASSETTE_IDS} NE '' then
                                If SRP_JSON(objField, 'New', 'Object') then
                                    SRP_JSON(objField, 'SetValue', 'label', 'Cassette ID', 'String')
                                    If SRP_JSON(arrayCassetteIDs, 'New', 'Array') then
                                        For Each CassetteID in CassetteIDs using @VM
                                            SRP_JSON(arrayCassetteIDs, 'AddValue', CassetteID, 'String')
                                        Next CassetteID
                                        SRP_JSON(objField, 'Set', 'values', arrayCassetteIDs)
                                        SRP_JSON(arrayCassetteIDs, 'Release')
                                    end
                                    SRP_JSON(arrayFields, 'Add', objField)
                                    SRP_JSON(objField, 'Release')
                                end
                            end
                            SRP_JSON(objForm, 'Set', 'fields', arrayFields)
                            SRP_JSON(arrayFields, 'Release')
                        end
                        SRP_JSON(objJSONScan, 'Set', '_form', objForm)
                        SRP_JSON(objForm, 'Release')
                    end
                    // Create the _class property for this resource.
                    SRP_JSON(objJSONScan, 'SetValue', '_class', 'resource')
                end
                jsonScan    = SRP_JSON(objJSONScan, 'Stringify', 'Styled')
                SRP_JSON(objJSONScan, 'Release')
            end else
                Error_Services('Add', 'Unable to create JSON representation in the ' : Service : ' service.')
            end
        end
    end else
        Error_Services('Add', 'ScanID argument was missing in the ' : Service : ' service.')
    end

    Response    = jsonScan

end service

//----------------------------------------------------------------------------------------------------------------------
// ConvertJSONScanToMV
//
// Converts a serialized JSON scan object into a MultiValue formatted SCANS row and returns the result.
//----------------------------------------------------------------------------------------------------------------------
Service ConvertJSONScanToMV(jsonScan)

    mvScan      = ''

    If jsonScan NE '' then
        If SRP_JSON(objJSONScan, 'Parse', jsonScan) EQ '' then

            mvScan<SCANS.CREATED_DATE$>             = SRP_JSON(objJSONScan, 'GetValue', 'created.date')
            mvScan<SCANS.CREATED_TIME$>             = SRP_JSON(objJSONScan, 'GetValue', 'created.time')
            mvScan<SCANS.SCAN_TYPE$>                = SRP_JSON(objJSONScan, 'GetValue', 'scan.type')
            mvScan<SCANS.EMPLOYEE_ID$>              = SRP_JSON(objJSONScan, 'GetValue', 'employee.ID')
            mvScan<SCANS.LOCATION_ID$>              = SRP_JSON(objJSONScan, 'GetValue', 'location.ID')
            mvScan<SCANS.TOOL_ID$>                  = SRP_JSON(objJSONScan, 'GetValue', 'tool.ID')
            mvScan<SCANS.LOAD_LOCK$>                = SRP_JSON(objJSONScan, 'GetValue', 'tool.loadLock')
            mvScan<SCANS.ACCEPTED$>                 = SRP_JSON(objJSONScan, 'GetValue', 'accepted.status', False$)
            mvScan<SCANS.ACCEPTED_DATE$>            = SRP_JSON(objJSONScan, 'GetValue', 'accepted.date')
            mvScan<SCANS.ACCEPTED_TIME$>            = SRP_JSON(objJSONScan, 'GetValue', 'accepted.time')
            mvScan<SCANS.ACTION$>                   = SRP_JSON(objJSONScan, 'GetValue', 'action')
            mvScan<SCANS.WAFER_COUNT_CONFIRMED$>    = SRP_JSON(objJSONScan, 'GetValue', 'waferCount.confirmed', False$)
            mvScan<SCANS.BOAT_ID$>                  = SRP_JSON(objJSONScan, 'GetValue', 'transfer.boatID')
            mvScan<SCANS.PL_NUMBER$>                = SRP_JSON(objJSONScan, 'GetValue', 'transfer.plNumber')
            mvScan<SCANS.UNLOAD_PL_CONFIRMED$>      = SRP_JSON(objJSONScan, 'GetValue', 'transfer.unloadplConfirmed', False$)
            mvScan<SCANS.TRANSFER_TOOL_ID$>         = SRP_JSON(objJSONScan, 'GetValue', 'transfer.tool.ID')

            arrayCassettes  = SRP_JSON(objJSONScan, 'Get', 'cassetteIDs')
            NumCassettes    = SRP_JSON(arrayCassettes, 'GetCount')
            For CassetteCnt = 1 to NumCassettes
                mvScan<SCANS.CASSETTE_IDS$, CassetteCnt>    = SRP_JSON(arrayCassettes, 'GetValue', CassetteCnt)
            Next CassetteCnt
            SRP_JSON(arrayCassettes, 'Release')

            arrayScanLog    = SRP_JSON(objJSONScan, 'Get', 'scan.scanLog')
            NumScanSessions = SRP_JSON(arrayScanLog, 'GetCount')
            For ScanSessionCnt = 1 to NumScanSessions
                objScan = SRP_JSON(arrayScanLog, 'Get', ScanSessionCnt)
                mvScan<SCANS.SCANNED_DATES$, ScanSessionCnt>    = SRP_JSON(objScan, 'GetValue', 'date')
                mvScan<SCANS.SCANNED_TIMES$, ScanSessionCnt>    = SRP_JSON(objScan, 'GetValue', 'time')
                mvScan<SCANS.SCANNED_DATA$, ScanSessionCnt>     = SRP_JSON(objScan, 'GetValue', 'data')
                SRP_JSON(objScan, 'Release')
            Next ScanSessionCnt
            SRP_JSON(arrayScanLog, 'Release')

            SRP_JSON(objJSONScan, 'Release')

        end else
            Error_Services('Add', 'Error parsing jsonScan in the ' : Service : ' service.')
        end
    end else
        Error_Services('Add', 'jsonScan argument was missing in the ' : Service : ' service.')
    end

    Response    = mvScan

end service


//----------------------------------------------------------------------------------------------------------------------
// DecodeScanData
//
// ScanData - The scan data containing encoded characters. - [Required]
//
// Decodes the scan data so that encoded characters are restored to their regular form. This returns the decoded scan
// data.
//----------------------------------------------------------------------------------------------------------------------
Service DecodeScanData(ScanData)

    DecodedScanData = ''

    If Len(ScanData) then
        DecodedScanData = ScanData
        Swap '/A' with '!' in DecodedScanData
        Swap '/B' with '"' in DecodedScanData
        Swap '/C' with '#' in DecodedScanData
        Swap '/D' with '$' in DecodedScanData
        Swap '/E' with '%' in DecodedScanData
        Swap '/F' with '&' in DecodedScanData
        Swap '/G' with "'" in DecodedScanData
        Swap '/H' with '(' in DecodedScanData
        Swap '/I' with ')' in DecodedScanData
        Swap '/J' with '*' in DecodedScanData
        Swap '/K' with '+' in DecodedScanData
        Swap '/L' with ',' in DecodedScanData
        Swap '/O' with '/' in DecodedScanData
        Swap '/Z' with ':' in DecodedScanData
        Swap '%F' with ';' in DecodedScanData
        Swap '%G' with '<' in DecodedScanData
        Swap '%H' with '=' in DecodedScanData
        Swap '%I' with '>' in DecodedScanData
        Swap '%J' with '?' in DecodedScanData
        Swap '%O' with '_' in DecodedScanData
    end else
        Error_Services('Add', 'The ScanData argument was missing in the ' : Service : ' service.')
    end

    Response    = DecodedScanData

end service


Service ResetDevData(CassetteIDs)
    
    DevCassettes = '277147,277425,277074,277265,277107,277106,276833,277006,277005,276833,276832,276895,276701,276700,277308,277118,277117,276604'
    Swap ',' with @VM in DevCassettes
    If CassetteIDs EQ '' then CassetteIDs = DevCassettes
    
    For each CassetteID in CassetteIDs using @VM
        // Reset RDS record
        RDSRec = Database_Services('ReadDataRow', 'RDS', CassetteID)
        If Error_Services('NoError') then
            If CassetteID EQ 277147 then
                RDSRec<RDS_DATE_OUT$>           = ''
                RDSRec<RDS_TIME_OUT$>           = ''
                RDSRec<RDS_OPERATOR_OUT$>       = ''
                RDSRec<RDS_WAFERS_OUT$>         = ''
                RDSRec<RDS_LWI_INST_ACK$>       = ''
                RDSRec<RDS_UNLOAD_INST_ACK$>    = ''
                Database_Services('WriteDataRow', 'RDS', CassetteID, RDSRec, True$, False$, True$)
                If Error_Services('NoError') then
                    WOMatKey = Xlate('RDS', CassetteID, 'WO_MAT_KEY', 'X')
                    WOMatRec = Database_Services('ReadDataRow', 'WO_MAT', WOMatKey)
                    If Error_Services('NoError') then
                        WOMatRec<WO_MAT_SIGNATURE$, 3> = ''
                        WOMatRec<WO_MAT_SIG_DTM$, 3>   = ''
                        Database_Services('WriteDataRow', 'WO_MAT', WOMatKey, WOMatRec, True$, False$, True$)
                    end
                end
            end else
                RDSRec<RDS_REACTOR$>            = ''
                RDSRec<RDS_DATE_IN$>            = ''
                RDSRec<RDS_DATE_OUT$>           = ''
                RDSRec<RDS_TIME_IN$>            = ''
                RDSRec<RDS_TIME_OUT$>           = ''
                RDSRec<RDS_OPERATOR_IN$>        = ''
                RDSRec<RDS_OPERATOR_OUT$>       = ''
                RDSRec<RDS_WAFERS_IN$>          = ''
                RDSRec<RDS_WAFERS_OUT$>         = ''
                RDSRec<RDS_VERIFY_QTY$>         = ''
                RDSRec<RDS_PRE_EPI_SIG$>        = ''
                RDSRec<RDS_PRE_EPI_SIG_DATE$>   = ''
                RDSRec<RDS_PRE_EPI_SIG_TIME$>   = ''
                RDSRec<RDS_LOAD_LOCK_SIDE$>     = ''
                RDSRec<RDS_SUPPL_ACK$>          = ''
                RDSRec<RDS_RDS_LAYER_ACK$>      = ''
                RDSRec<RDS_PRE_INST_ACK$>       = ''
                RDSRec<RDS_FWI_INST_ACK$>       = ''
                RDSRec<RDS_LWI_INST_ACK$>       = ''
                RDSRec<RDS_QA_INST_ACK$>        = ''
                RDSRec<RDS_LOAD_INST_ACK$>      = ''
                RDSRec<RDS_UNLOAD_INST_ACK$>    = ''
                RDSRec<RDS_WAFER_COUNT_ACK$>    = ''
                RDSRec<RDS_SUPPL_SIG$>          = ''
                RDSRec<RDS_SUPPL_SIG_DATE$>     = ''
                RDSRec<RDS_SUPPL_SIG_TIME$>     = ''
                Database_Services('WriteDataRow', 'RDS', CassetteID, RDSRec, True$, False$, True$)
                If Error_Services('NoError') then
                    WOMatKey = Xlate('RDS', CassetteID, 'WO_MAT_KEY', 'X')
                    WOMatRec = Database_Services('ReadDataRow', 'WO_MAT', WOMatKey)
                    If Error_Services('NoError') then
                        WOMatRec<WO_MAT_SIGNATURE$> = ''
                        WOMatRec<WO_MAT_SIG_DTM$>   = ''
                        Database_Services('WriteDataRow', 'WO_MAT', WOMatKey, WOMatRec, True$, False$, True$)
                    end
                end
                RDSLayerKeys = RDSRec<RDS_RDS_LAYER_KEYS$>
                For each RDSLayerKey in RDSLayerKeys using @VM
                    RDSLayerRec = Database_Services('ReadDataRow', 'RDS_LAYER', RDSLayerKey)
*                    RDSLayerRec<RDS_LAYER_DOPANT$>            = ''
*                    RDSLayerRec<RDS_LAYER_EPI_DILUENT$>       = ''
                    RDSLayerRec<RDS_LAYER_EPI_TIME$>          = ''
                    RDSLayerRec<RDS_LAYER_DILUENT_ADJ_PARAM$> = ''
                    RDSLayerRec<RDS_LAYER_DOPANT_FLOW$>       = ''
                    RDSLayerRec<RDS_LAYER_HCL_FLOW$>          = ''
                    RDSLayerRec<RDS_LAYER_BAKE_TIME$>         = ''
                    RDSLayerRec<RDS_LAYER_EPI_H2_FLOW$>       = ''
                    RDSLayerRec<RDS_LAYER_TCS_FLOW$>          = ''
                    RDSLayerRec<RDS_LAYER_DCS_FLOW$>          = ''
                    RDSLayerRec<RDS_LAYER_AUX1$>              = ''
                    RDSLayerRec<RDS_LAYER_AUX2$>              = ''
                    RDSLayerRec<RDS_LAYER_F_OFFSET$>          = ''
                    RDSLayerRec<RDS_LAYER_S_OFFSET$>          = ''
                    RDSLayerRec<RDS_LAYER_R_OFFSET$>          = ''
                    RDSLayerRec<RDS_LAYER_ETCH1$>             = ''
                    RDSLayerRec<RDS_LAYER_ETCH2$>             = ''
                    RDSLayerRec<RDS_LAYER_ETCH3$>             = ''
                    RDSLayerRec<RDS_LAYER_OVERGROW_REQ$>      = ''
                    RDSLayerRec<RDS_LAYER_SUSC_ETCH$>         = ''
                    RDSLayerRec<RDS_LAYER_UL_TEMP$>           = ''
                    Database_Services('WriteDataRow', 'RDS_LAYER', RDSLayerKey, RDSLayerRec, True$, False$, True$)
                Next RDSLayerKey
            end
        end
    Next CassetteID
    
end service


Service RemoveOldScans()
    
    CleanDate = OConv((Date() - 30), 'D4/')
    Query = 'SELECT SCANS WITH CREATED_DATE LT ':Quote(CleanDate)
    GoSub ClearCursors
    RList(Query, TARGET_ACTIVELIST$, '', '', '')
    If Not(Get_Status(errCode)) then
        Done = False$
        Loop
            Readnext KeyID else Done = True$
        Until Done
            Database_Services('DeleteDataRow', 'SCANS', KeyID)
        Repeat
    end
    
end service


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


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







