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.
        06/13/24	djm		Add new stage-specific supplement system.

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

#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
$Insert SUPPLEMENTS_EQUATES
$Insert Lot_Equates
$Insert Test_Run_Type_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, Supplement_Services
Declare function    Test_Run_Services, Lot_Services, WO_MAT_Services, Reactor_Log_Services, Schedule_Services
Declare function	GetTickCount
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, Supplement_Services
Declare Subroutine  Test_Run_Services, WO_MAT_Services, Reactor_Log_Services, Schedule_Services, Mona_Services
Declare Subroutine  Dialog_Response_Log_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

IsProd = Environment_Services('IsProd')
If IsProd EQ True$ then
	MonaResource = 'GRP_OPENINSIGHT_MES_OP_FE_SCANSERVICES'
end else
	MonaResource = 'GRP_OPENINSIGHT_MES_OP_FE_DEV_SCANSERVICES'
end

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)
	StartTick = GetTickCount()
    MetricName = 'GetScansRow'
	
	ScansRow    = ''
	
	ErrMsg = ''
	If ScanID NE '' then
		ScansRow    = Database_Services('ReadDataRow', 'SCANS', ScanID)
		If ReturnJSON EQ True$ then
			ScansRow    = Scan_Services('ConvertMVScanToJSON', ScanID, ScansRow)
		end
	end else
		ErrMsg = 'ScanID argument was missing in the ' : Service : ' service.'
	end
	
	Response    = ScansRow
	
	EndTick = GetTickCount()
    Mona_Services('QueueLatencyAndCountMetrics', MonaResource, MetricName, StartTick, EndTick)
    
    If ErrMsg NE '' then Error_Services('Add', ErrMsg)
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)
	StartTick = GetTickCount()
    MetricName = 'SetScansRow'
	
	ErrMsg = ''
	If (ScanID NE '') AND (ScansRow NE '') then
		Database_Services('WriteDataRow', 'SCANS', ScanID, ScansRow)
	end else
		ErrMsg = 'ScanID or ScansRow argument was missing in the ' : Service : ' service.'
	end
	
	EndTick = GetTickCount()
    Mona_Services('QueueLatencyAndCountMetrics', MonaResource, MetricName, StartTick, EndTick)
    
    If ErrMsg NE '' then Error_Services('Add', ErrMsg)
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)
	StartTick = GetTickCount()
    MetricName = 'ProcessScanData'
	
	ErrMsg = ''
	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
						LotID = ScanData[3, 999]
						Begin Case
							Case ( RowExists('RDS', LotId) EQ True$ AND ScanData[1, 2] EQ '1T')
								ScansRow<SCANS.CASSETTE_IDS$> = LotID
							Case ScanData[1, 4] EQ '1TTW'
								ValidTWLot                     = False$
								If RowExists('LOT', LotId) then
									TWLots = ScansRow<SCANS.TW_LOT_ID$>
									Locate LotId in TWLots using @VM setting twPos then
										ErrMsg = LotID : ' has already been added as a test wafer lot being used.'
									end else
										ScansRow<SCANS.TW_LOT_CHANGED$> = True$
										LotCurrentOperation = Lot_Services('GetLotCurrOperationName', LotId)
										If LotCurrentOperation NE 'TW_CREATE' AND LotCurrentOperation NE 'TW_CLOSE' then
											LotCurrWfrQty = Database_Services('ReadDataColumn', 'LOT', LotId, LOT_WAFER_QTY$, True$, 0, False$)
											If LotCurrWfrQty GT 0 then
												If ScansRow<SCANS.TW_LOT_ID$> EQ '' AND ScansRow<SCANS.CASSETTE_IDS$> NE '' then
													ScansRow<SCANS.TEST_RUN_TYPE_ID$> = 4;//Standard Sampling
												end
												ScansRow<SCANS.TW_LOT_ID$, -1> = LotID
												ThisScanTWLots = ScansRow<SCANS.TW_LOT_ID$>
												Locate LotID in ThisScanTWLots using @VM setting twPOS then
													ScansRow<SCANS.TW_LOT_QTY$, twPos> = 1
												end
											end else
												ErrMsg = LotID : ' does not have enough wafers to be used.'
											end
										end else
											ErrMsg = LotID : ' is not at a valid operation for use.'
										end
									end
								end else
									ErrMsg = LotID : ' is not a valid test wafer lot ID.'
								end
								
							Case Otherwise$
								ScansRow<SCANS.SUPPLIER_LOT$> = LotID
						End Case                     
					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}
								ErrMsg = EmployeeNotAuthorizedReason
							Case EmployeeActive NE True$
								ErrMsg = 'Inactive employee.'
							Case Otherwise$
								ScansRow<SCANS.EMPLOYEE_ID$>    = {EMPLOYEE_ID}
								
								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
								
								ScansRow<SCANS.IS_AUTHORIZED_TO_OVERRIDE_ROTR$> = Member
								ScansRow<SCANS.EMPLOYEE_CHANGED$> = True$
						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}
										ErrMsg = 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
														ErrMsg = 'Invalid load lock value "':LoadLock:'".'
													end
												end else
													ErrMsg = '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'
									ErrMsg = '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$
									ErrMsg = 'Tool type ':ToolType:' is not currently supported by the barcode application.'
							End Case
						end else
							ErrMsg = 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
							ErrMsg = 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
							ErrMsg = 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
									ErrMsg = 'Invalid password for user ':EmployeeID:'.'
								end
								ScansRow<SCANS.EMPLOYEE_CHANGED$> = True$
							end else
								ErrMsg = 'An employee ID must be scanned before scanning a password.'
							end
						end else
							ErrMsg = '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                       = Database_Services('ReadDataRow', 'REACTOR', Reactor, True$, 0, False$)
									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.OVERRIDE_REQD$> = 0
									ScansRow<SCANS.OVERRIDE_TYPE$> = ''
									ScansRow<SCANS.OVERRIDE_REASON$> = ''
									ScansRow<SCANS.EMPLOYEE_ID$>= ''
									ScansRow<SCANS.AUTHENTICATED$> = False$
									ScansRow<SCANS.IS_AUTHORIZED_TO_OVERRIDE_ROTR$> = False$
									ScansRow<SCANS.EMPLOYEE_CHANGED$> = True$
									Result = 'Override Performed successfully'
								end
								
							end else
								ErrMsg = '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[1, 16] EQ 'TWQUANTITYUPDATE'
						
						TWLot          = Field(ScanData, '|', 2)
						TWLotQuantity  = Field(ScanData, '|', 3)
						ThisScanTWLots = ScansRow<SCANS.TW_LOT_ID$>
						
						Locate TWLot in ThisScanTWLots using @VM setting twPOS then
							ScansRow<SCANS.TW_LOT_QTY$, twPos> = TWLotQuantity
							ScansRow<SCANS.TW_LOT_CHANGED$> = True$
						end
						
					Case ScanData[1, 17] EQ 'TESTRUNTYPEUPDATE'
						TestRunTypeId                     = Field(ScanData, '|', 2)
						ScansRow<SCANS.TEST_RUN_TYPE_ID$> = TestRunTypeId
						
					Case Len(ScanData) GE 22 and ScanData[1, 22] EQ 'LAST_CASSETTE_TEST_ACK'
						RDSNo = {CASSETTE_IDS}
						Reactor = {TOOL_ID}[2,999]
						
						WO = Xlate('RDS', RDSNo, RDS_WO$, 'X')
						CassNo = Xlate('RDS', RDSNo, 'RUN_ORDER_NUM', 'X')
						WoMatKey = WO:'*':CassNo
						WoMatRecord = Database_Services('ReadDataRow', 'WO_MAT', WoMatKey)
						
						WoMatRecord<WO_MAT_LAST_CASSETTE_TEST_WAFER_ACKED$> = True$
						
						Database_Services('WriteDataRow', 'WO_MAT', WoMatKey, WoMatRecord, True$, False$, False$)
						
						ConfirmNote = 'Test wafer requirement acknowledged. [RDS:' : RDSNo : '], [WO:' : WO : '], [Cassette:' : CassNo : ']'
						Reactor_Log_Services('AddComment', Reactor, ConfirmNote, {EMPLOYEE_ID})
						
						Result = 'Test wafer requirement acknowledged'
					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
							ErrMsg = CassetteID : ' is not a valid Cassette ID.'
						end
				End Case
				
				If ErrMsg EQ '' 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
								ErrMsg = '(':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
								TestWaferLotData = ScansRow<SCANS.TW_LOT_ID$>
								TestWaferLotChanged = ScansRow<SCANS.TW_LOT_CHANGED$>
								Abort            = False$
								TestWaferLotIsValid = False$
								If TestWaferLotData NE '' and TestWaferLotChanged NE False$ then
									ThisTestRunType = ScansRow<SCANS.TEST_RUN_TYPE_ID$>
									If ThisTestRunType NE '' then
										for each TWLot in TestWaferLotData using @VM setting twPOS
											If ScansRow<SCANS.TW_LOT_QTY$, twPOS> NE '' OR ScansRow<SCANS.TW_LOT_QTY$, twPOS> GT 0 then
												TWLotCurrQty     = Database_Services('ReadDataColumn', 'LOT', TWLot, LOT_WAFER_QTY$, True$, 0, False$)
												ThisCurrUsageQty = ScansRow<SCANS.TW_LOT_QTY$, twPOS>
												If TWLotCurrQty GT 0 then
													If TWLotCurrQty GE ThisCurrUsageQty then
														TWLotCurrOperation = Lot_Services('GetLotCurrOperationName', TWLot)
														If TWLotCurrOperation NE 'TW_CREATE' AND TWLotCurrOperation NE 'TW_CLOSE' then
															TWLotCurrOpen = Database_Services('ReadDataColumn', 'LOT', TWLot, LOT_OPEN$, True$, 0, False$)
															If Not(TWLotCurrOpen) then
																Abort = True$
																Scan_Services('AddNotAcceptableReason', 'Lot ':TWLot:' is closed and cannot be used at this time.')
															end else
																TestWaferLotIsValid = True$
															end
														end else
															Abort = True$
															Scan_Services('AddNotAcceptableReason', 'Lot ':TWLot:' is currently at ':TWLotCurrOperation:' and cannot be used at this time.')
														end
													end else
														Scan_Services('AddNotAcceptableReason', 'Test wafer Lot ':TWLot:' does not have enough wafers. Please select ' : TWLotCurrQty : ' wafers or less.')
													end
												end else
													Scan_Services('AddNotAcceptableReason', 'Test wafer Lot ':TWLot:' has no wafers and cannot be logged for usage.')
												end  
											end else
												Abort = True$
												Scan_Services('AddNotAcceptableReason', 'Test Wafer Lot ':TWLot:' must have a quantity associated with it.')
											end
										Until Abort    
										Next TWLot
									end else
										Scan_Services('AddNotAcceptableReason', 'A test run type is required to log test wafer usage.')
									end
								end
								// Ensure only one cassette is scanned
								If Not(Abort) then
									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
													ErrMsg = 'Cassette ':CassetteID:' failed due to missing PTI.'
												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) else
														ErrMsg = 'Cassette ':CassetteID:' failed due to missing PTI.'
													end
												Next OutCassNo
											end     
											
											If ErrMsg EQ '' then
												If CurrStatus NE 'HOLD' then
													If (EmployeeID NE '') then
														*                                                    SupplInstAckReq         = Xlate('RDS', RDSNo, 'SUPPL_ACK_REQ'         , 'X')
														CurrStage = Xlate('RDS', RDSNo, 'CURR_STAGE', 'X')
														If CurrStage EQ 'VER' then
															VerSupplID = Supplement_Services('UnacknowledgedSupplementCheck', 'RDS', RDSNo, 'VER')
															If VerSupplID NE False$ then
																SupplVerInstAckReq = True$
															end else
																SupplVerInstAckReq = False$
															end
														end else
															SupplVerInstAckReq = False$
														end
														
														If CurrStage EQ 'LOAD' OR CurrStage EQ 'VER' then
															LoadSupplID = Supplement_Services('UnacknowledgedSupplementCheck', 'RDS', RDSNo, 'LOAD')
															If LoadSupplID NE False$ then
																SupplLoadInstAckReq = True$
															end else
																SupplLoadInstAckReq = False$
															end
														end else
															SupplLoadInstAckReq = False$
														end
														
														If CurrStage EQ 'UNLOAD' then
															UnloadSupplID = Supplement_Services('UnacknowledgedSupplementCheck', 'RDS', RDSNo, 'UNLOAD')
															If UnloadSupplID NE False$ then
																SupplUnloadInstAckReq = True$
															end else
																SupplUnloadInstAckReq = False$
															end                                                            
														end else
															SupplUnloadInstAckReq = False$
														end
														
														If CurrStage EQ 'POST' then
															PostSupplID = Supplement_Services('UnacknowledgedSupplementCheck', 'RDS', RDSNo, 'POST')
															If PostSupplID NE False$ then
																SupplPostInstAckReq = True$
															end else
																SupplPostInstAckReq = False$
															end
														end else
															SupplPostInstAckReq = False$
														end
														
														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 (SupplVerInstAckReq NE False$) then
																SupplAck = SRP_JSON(hRDS, 'GetValue', 'supplVerInstAck')
																If (SupplAck EQ True$) then
																	Supplement_Services('AcknowledgeSupplement', VerSupplID, ScansRow<SCANS.EMPLOYEE_ID$>)
																end
															end
															If (SupplLoadInstAckReq NE False$) then
																SupplAck = SRP_JSON(hRDS, 'GetValue', 'supplLoadInstAck')
																If (SupplAck EQ True$) then
																	Supplement_Services('AcknowledgeSupplement', LoadSupplID, ScansRow<SCANS.EMPLOYEE_ID$>)
																end
															end
															If (SupplUnloadInstAckReq NE False$) then
																SupplAck = SRP_JSON(hRDS, 'GetValue', 'supplUnloadInstAck')
																If (SupplAck EQ True$) then
																	Supplement_Services('AcknowledgeSupplement', UnloadSupplID, ScansRow<SCANS.EMPLOYEE_ID$>)
																end
															end
															If (SupplPostInstAckReq NE False$) then
																SupplAck = SRP_JSON(hRDS, 'GetValue', 'supplPostInstAck')
																If (SupplAck EQ True$) then
																	Supplement_Services('AcknowledgeSupplement', PostSupplID, ScansRow<SCANS.EMPLOYEE_ID$>)
																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')
															CurrStage = Xlate('RDS', RDSNo, 'CURR_STAGE', 'X')
															If CurrStage EQ 'VER' then
																VerSupplID = Supplement_Services('UnacknowledgedSupplementCheck', 'RDS', RDSNo, 'VER')
																If VerSupplID NE False$ then
																	SupplVerInstAckReq = True$
																end else
																	SupplVerInstAckReq = False$
																end
															end else
																SupplVerInstAckReq = False$
															end
															
															If CurrStage EQ 'LOAD' OR CurrStage EQ 'VER' then
																LoadSupplID = Supplement_Services('UnacknowledgedSupplementCheck', 'RDS', RDSNo, 'LOAD')
																If LoadSupplID NE False$ then
																	SupplLoadInstAckReq = True$
																end else
																	SupplLoadInstAckReq = False$
																end
															end else
																SupplLoadInstAckReq = False$
															end
															
															If CurrStage EQ 'UNLOAD' then
																UnloadSupplID = Supplement_Services('UnacknowledgedSupplementCheck', 'RDS', RDSNo, 'UNLOAD')
																If UnloadSupplID NE False$ then
																	SupplUnloadInstAckReq = True$
																end else
																	SupplUnloadInstAckReq = False$
																end
															end else
																SupplUnloadInstAckReq = False$
															end
															
															If CurrStage EQ 'POST' then
																PostSupplID = Supplement_Services('UnacknowledgedSupplementCheck', 'RDS', RDSNo, 'POST')
																If PostSupplID NE False$ then
																	SupplPostInstAckReq = True$
																end else
																	SupplPostInstAckReq = False$
																end
															end else
																SupplPostInstAckReq = False$
															end
															
															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 'VER'
																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)
																		//Test Wafer logging verification
																		If Error_Services('NoError') then
																			// Supplier lot verification
																			ScannedSuppLot = ScansRow<SCANS.SUPPLIER_LOT$>
																			RDSSuppLot     = RDSRec<RDS_LOT_NUM$>
																			If (ScannedSuppLot NE '') then
																				If (ScannedSuppLot _EQSC RDSSuppLot) then
																					IsTWLoggingReqd = RDS_Services('IsTWLoggingReqd', RDSNo)
																					If IsTWLoggingReqd NE True$ OR TestWaferLotData NE '' 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
																								RDSLayerAckReq  = Xlate('RDS', RDSNo, 'RDS_LAYER_ACK_REQ', 'X')
																								If RDSLayerAckReq EQ False$ then
																									LoadStageReady  = QA_Services('LoadSignatureReady', RDSNo, Username, WaferQty, LLSide, True$, Reactor)
																									If (LoadStageReady NE True$) then 
																										// Why is it not ready?
																										ErrMsg = Error_Services('GetMessage')
																										Begin Case
																											Case IndexC(ErrMsg, 'supplement', 1)
																												// Clear the error to return a JSON payload and handle the acknowledgements.
																												Error_Services('Clear')
																												Scan_Services('AddNotAcceptableReason', ErrMsg)
																											Case IndexC(ErrMsg, 'ROTR', 1)
																												ROTRBlock       = Database_Services('ReadDataColumn', 'REACTOR', Reactor, REACTOR_ROTR_STATUS$, True$, 0, False$)
																												ROTRBlockReason = Database_Services('ReadDataColumn', 'REACTOR', Reactor, REACTOR_ROTR_STATUS_REASON$, True$, 0, False$)
																												ROTREnabled     = Database_Services('ReadDataColumn', 'REACTOR', Reactor, REACTOR_ENABLE_ROTR$, True$, 0, False$)
																												If ( (ROTRBlock NE 'P') AND (ROTREnabled EQ True$) ) then
																													// Clear the error to return a JSON payload and allow for OVERRIDE scan.
																													ScansRow<SCANS.OVERRIDE_REQD$>   = True$
																													ScansRow<SCANS.OVERRIDE_REASON$> = ROTRBlockReason
																													ScansRow<SCANS.OVERRIDE_TYPE$>   = 'ROTR'
																													Error_Services('Clear')
																													ErrMsg                           = ''
																													Scan_Services('AddNotAcceptableReason', "ROTR Load Block Enabled")
																												end else
																													ScansRow<SCANS.OVERRIDE_REQD$>   = False$
																													ScansRow<SCANS.OVERRIDE_REASON$> = ''
																													ScansRow<SCANS.OVERRIDE_TYPE$>   = ''
																												end
																										End Case
																									end
																								end else
																									Scan_Services('AddNotAcceptableReason', 'RDS layer parameters must be reviewed for accuracy and acknowledged before the load operation can be signed.')
																								end
																							end else
																								// Why is it not ready?
																								ErrMsg = Error_Services('GetMessage')
																								Begin Case
																									Case IndexC(ErrMsg, 'supplement', 1)
																										Error_Services('Clear')
																										Scan_Services('AddNotAcceptableReason', ErrMsg)
																								End Case																					
																							end
																						end else
																							Scan_Services('AddNotAcceptableReason', 'The cassette wafer count must be verified against the scheduled wafer count to proceed.')
																						end
																					end else
																						Scan_Services('AddNotAcceptableReason', 'Test wafers are required to be logged with this run.')
																					end
																				end else
																					ScansRow<SCANS.SUPPLIER_LOT$> = ''
																					ErrMsg = '(':CassetteID:') Supplier lot mismatch.'
																				end
																			end else
																				Scan_Services('AddNotAcceptableReason', 'Supplier lot scan required in order to complete a tool scan.')
																			end
																		end else
																			ErrMsg = Error_Services('GetMessage')
																		end
																	end else
																		ErrMsg = '(':CassetteID:') The first run must be completed using the OpenInsight user interface.'
																	end                                               
																end else
																	ErrMsg = '(':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 _EQSC RDSSuppLot) then
																		IsTWLoggingReqd = RDS_Services('IsTWLoggingReqd', RDSNo)
																		If IsTWLoggingReqd NE True$ OR TestWaferLotData NE '' then
																			// Scheduled tool verification (only supports reactors at the moment)
																			ClearSelect
																			SchedTool = Xlate('RDS', RDSNo, 'SCHED_REACTOR', 'X')
																			ClearSelect
																			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)
																							If (LoadStageReady NE True$) then 
																								// Why is it not ready?
																								ErrMsg = Error_Services('GetMessage')
																								Begin Case
																									Case IndexC(ErrMsg, 'supplement', 1)
																										// Clear the error to return a JSON payload and handle the acknowledgements.
																										Error_Services('Clear')
																										Scan_Services('AddNotAcceptableReason', ErrMsg)
																									Case IndexC(ErrMsg, 'ROTR', 1)
																										ROTRBlock       = Database_Services('ReadDataColumn', 'REACTOR', Reactor, REACTOR_ROTR_STATUS$, True$, 0, False$)
																										ROTRBlockReason = Database_Services('ReadDataColumn', 'REACTOR', Reactor, REACTOR_ROTR_STATUS_REASON$, True$, 0, False$)
																										ROTREnabled     = Database_Services('ReadDataColumn', 'REACTOR', Reactor, REACTOR_ENABLE_ROTR$, True$, 0, False$)
																										If ( (ROTRBlock NE 'P') AND (ROTREnabled EQ True$) ) then
																											// Clear the error to return a JSON payload and allow for OVERRIDE scan.
																											ScansRow<SCANS.OVERRIDE_REQD$>   = True$
																											ScansRow<SCANS.OVERRIDE_REASON$> = ROTRBlockReason
																											ScansRow<SCANS.OVERRIDE_TYPE$>   = 'ROTR'
																											Error_Services('Clear')
																											ErrMsg                           = ''
																											Scan_Services('AddNotAcceptableReason', "ROTR Load Block Enabled")
																										end else
																											ScansRow<SCANS.OVERRIDE_REQD$>   = False$
																											ScansRow<SCANS.OVERRIDE_REASON$> = ''
																											ScansRow<SCANS.OVERRIDE_TYPE$>   = ''
																										end
																								End Case
																							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
																					Scan_Services('AddNotAcceptableReason', 'RDS layer parameters must be reviewed for accuracy and acknowledged before the load operation can be signed.')
																				end
																			end else
																				ErrMsg = 'Scanned tool ':ScanTool:' does not match the scheduled tool ':SchedTool:'. (':RDSNo:')'
																			end
																		end else
																			Scan_Services('AddNotAcceptableReason', 'Test wafers are required to be logged with this run.')
																		end
																	end else
																		
																		ScansRow<SCANS.SUPPLIER_LOT$> = ''
																		ErrMsg = '(':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)
																			If Not(UnloadStageReady) then
																				// Why is it not ready?
																				ErrMsg = Error_Services('GetMessage')
																				Begin Case
																					Case IndexC(ErrMsg, 'supplement', 1)
																						Error_Services('Clear')
																						Scan_Services('AddNotAcceptableReason', ErrMsg)
																					Case Otherwise$
																						// Keep error on Error_Services stack and return 400 level error.
																				End Case
																			end else
																				ReactorNo = Xlate('RDS', RDSNo, RDS_REACTOR$, 'X')
																				ReactorType = Xlate('REACTOR', ReactorNo, REACTOR_REACT_TYPE$, 'X')
																				If Len(ReactorType) GE 3 and ReactorType[1, 3] _EQC 'HTR' then
																					WO = Xlate('RDS', RDSNo, RDS_WO$, 'X')
																					CassNo = Xlate('RDS', RDSNo, 'RUN_ORDER_NUM', 'X')
																					WoMatKey = WO:'*':CassNo
																					WoMatRec = Database_Services('ReadDataRow', 'WO_MAT', WoMatKey)
																					if Error_Services('NoError') then
																						If WoMatRec NE '' then
																							LastCassInWoTestWaferAckReq = WoMatRec<WO_MAT_LAST_CASSETTE_TEST_WAFER_ACK_REQ$>
																							LastCassInWoTestWaferAcked = WoMatRec<WO_MAT_LAST_CASSETTE_TEST_WAFER_ACKED$>
																							LastCassInWoTestWaferReqMsg = ''
																							If LastCassInWoTestWaferAcked EQ False$ or LastCassInWoTestWaferAcked EQ '' then
																								LastCassInWo = WO_MAT_Services('CassetteIsLastInWo', WoMatKey)
																								
																								If LastCassInWo then
																									NextEventScheduled = False$
																									NextEventHasSamePsn = True$
																									NextEventIsBlock = False$
																									
																									NextScheduledEvent = Schedule_Services('GetNextEvent', ReactorNo)
																									If Error_Services('NoError') then
																										NextEventScheduled = True$
																										
																										NextEventHasSamePsn = Schedule_Services('NextEventIsSamePsn', ReactorNo)
																										NextEventIsBlock = Schedule_Services('NextEventIsBlock', ReactorNo)
																									end
																									
																									If NextEventScheduled EQ False$ or NextEventHasSamePsn EQ False$ or NextEventIsBlock EQ True$ then
																										LastCassInWoTestWaferAckReq = True$
																										WoMatRec<WO_MAT_LAST_CASSETTE_TEST_WAFER_ACK_REQ$> = LastCassInWoTestWaferAckReq
																										TestWaferRanSinceLoad = WoMatRec<WO_MAT_LAST_CASSETTE_TEST_WAFER_RAN$> or TestWaferLotIsValid EQ True$
											
																										If TestWaferLotIsValid NE True$ and TestWaferRanSinceLoad NE True$ then
																											TestWaferRanSinceLoad = Rds_Services('TestWaferRanAfterLoad', RDSNo)
																											
																											If TestWaferRanSinceLoad NE True$ then
																												LastCassInWoTestWaferReqMsg = ''
																												If NextEventScheduled EQ False$ or NextEventIsBlock EQ True$ then
																													LastCassInWoTestWaferReqMsg = 'No material scheduled. Test wafer required.'
																													Dialog_Response_Log_Services('AddDialogResponseLog', RDSNo, 'RDS', @User4, 'HTR_LAST_BOX_TW', 'No test wafer ran with box when one was required. Unload signature was blocked. User was notified.')
																												end else If NextEventHasSamePsn EQ False$ then
																													LastCassInWoTestWaferReqMsg = 'Next work order is a NEW PSN number. Test wafer required.'
																													Dialog_Response_Log_Services('AddDialogResponseLog', RDSNo, 'RDS', @User4, 'HTR_LAST_BOX_TW', 'No test wafer ran with box when one was required. Unload signature was blocked. User was notified.')
																												end
																												
																												Scan_Services('AddNotAcceptableReason', LastCassInWoTestWaferReqMsg)
																											end
																										end else
																										    Dialog_Response_Log_Services('AddDialogResponseLog', RDSNo, 'RDS', @User4, 'HTR_LAST_BOX_TW', 'Test wafer was already logged with box. Unload signature was allowed. User was not prompted.')
																										end
																										
																										WoMatRec<WO_MAT_LAST_CASSETTE_TEST_WAFER_RAN$> = TestWaferRanSinceLoad
																										
																										Database_Services('WriteDataRow', 'WO_MAT', WoMatKey, WoMatRec, True$, False$, False$)
																									end
																								end
																							end
																						end else
																							ErrMsg = 'WO_MAT record was null.'
																						end
																					end else
																						ErrMsg = 'Failure to read cassette record. ' :  Error_Services('GetMessage')
																					end
																				end
																			end
																		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 'COMP'
																Action = 'COMP'
																ErrMsg = '(':CassetteID:") Cassette has already been FQA'd."
															Case Otherwise$
																Action = CurrStage
																ErrMsg = '(':CassetteID:') The ':CurrStage:' is not currently supported by the barcode application.'
														End Case
													end else
														Scan_Services('AddNotAcceptableReason', 'EmployeeID required to complete a tool scan.')                      
													end
												end else
													ErrMsg = '(':CassetteID:') Process Error: cassette is on Hold and may not be signed off.'
												end                                           
											end
										end else
											ErrMsg = '(':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
										ErrMsg = '(':CassetteID:') Only one cassette can be loaded onto a tool at a time.'
									end
								end
								
							end else
								If ScansRow<SCANS.TW_LOT_ID$> NE '' then
									TestWaferLotChanged = ScansRow<SCANS.TW_LOT_CHANGED$>
									ThisTestRunType = ScansRow<SCANS.TEST_RUN_TYPE_ID$>
									ThisUser        = ScansRow<SCANS.EMPLOYEE_ID$>
									If TestWaferLotChanged NE False$ then
										If ThisTestRunType NE '' then
											If RowExists('LSL_USERS', ThisUser) then
												TestWaferLotData = ScansRow<SCANS.TW_LOT_ID$>
												Abort            = False$
												for each TWLot in TestWaferLotData using @VM setting twPOS
													If ScansRow<SCANS.TW_LOT_QTY$, twPOS> NE '' OR ScansRow<SCANS.TW_LOT_QTY$, twPOS> GT 0 then
														TWLotCurrQty     = Database_Services('ReadDataColumn', 'LOT', TWLot, LOT_WAFER_QTY$, True$, 0, False$)
														ThisCurrUsageQty = ScansRow<SCANS.TW_LOT_QTY$, twPOS>
														If TWLotCurrQty GT 0 then
															If TWLotCurrQty GE ThisCurrUsageQty then
																TWLotCurrOperation = Lot_Services('GetLotCurrOperationName', TWLot)
																If TWLotCurrOperation NE 'TW_CREATE' AND TWLotCurrOperation NE 'TW_CLOSE' then
																	TWLotCurrOpen = Database_Services('ReadDataColumn', 'LOT', TWLot, LOT_OPEN$, True$, 0, False$)
																	If Not(TWLotCurrOpen) then
																		
																		Abort           = True$
																		Scan_Services('AddNotAcceptableReason', 'Lot ':TWLot:' is closed and cannot be used at this time.')
																	end
																end else
																	Abort = True$
																	Scan_Services('AddNotAcceptableReason', 'Lot ':TWLot:' is currently at ':TWLotCurrOperation:' and cannot be used at this time.')
																end
															end else
																Scan_Services('AddNotAcceptableReason', 'Test wafer Lot ':TWLot:' does not have enough wafers. Please select ' : TWLotCurrQty : ' wafers or less.')
															end
														end else
															Scan_Services('AddNotAcceptableReason', 'Test wafer Lot ':TWLot:' has no wafers and cannot be logged for usage.')
														end  
													end else
														Abort = True$
														Scan_Services('AddNotAcceptableReason', 'Lot ':TWLot:' must have a quantity associated with it.')
													end
												Until Abort    
												Next TWLot
											end else
												Scan_Services('AddNotAcceptableReason', 'A User ID Scan is required to log test wafer usage.')
											end
										end else
											Scan_Services('AddNotAcceptableReason', 'A test run type is required to log test wafer usage.')
										end
									end
								end else
									Scan_Services('AddNotAcceptableReason', 'A cassette must be scanned in order to complete a tool scan.')
								end
							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 ErrMsg EQ '' 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$>  = ErrMsg                           
				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
				
				Database_Services('WriteDataRow', 'SCANS', ScanID, ScansRow, True$, False$, True$)                           
			end
		end else
			ErrMsg = 'Unable to parse the JSON scan data in the ':Service:' service.'
		end
		SRP_JSON(hScanJSON, 'Release')
	end else
		ErrMsg = 'ScanID or ScanJSON argument was missing in the ' : Service : ' service.'
	end
	
	EndTick = GetTickCount()
    Mona_Services('QueueLatencyAndCountMetrics', MonaResource, MetricName, StartTick, EndTick)
    
    If ErrMsg NE '' then Error_Services('Add', ErrMsg)
end service


//----------------------------------------------------------------------------------------------------------------------
// AcceptScan
//
//----------------------------------------------------------------------------------------------------------------------
Service AcceptScan(ScanID, ScanJSON)
	StartTick = GetTickCount()
    MetricName = 'AcceptScan'
	
	ErrMsg = ''
	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
								ErrMsg = 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
								ErrMsg = 'Error calling obj_WO_Mat_Log("Create"). Error code: ':errCode
							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>
							*if CassetteId EQ 1005860 then debug
							TestWaferLots = ScansRow<SCANS.TW_LOT_ID$>
							Begin Case
								Case CassetteID NE ''
									//Tool Cassette scan, check for test wafers after everything is signed.
									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 'VER'
											Action = 'LOAD'
											// Check if both the PRE and LOAD stages are ready to sign
											PreStageSigned  = False$
											LoadStageSigned = False$
											//Log test wafer usage here
											If TestWaferLots NE '' then
												Continue         = False$
												ToolID           = ScansRow<SCANS.TOOL_ID$>
												Reactor          = ToolID[-1, 'BR']
												Username         = ScansRow<SCANS.EMPLOYEE_ID$>
												TestWaferLotQtys = ScansRow<SCANS.TW_LOT_QTY$>
												TestRunType = ScansRow<SCANS.TEST_RUN_TYPE_ID$>
												PSNo             = Database_Services('ReadDataColumn', 'RDS', RDSNo, RDS_PROD_SPEC_ID$, True$, 0, False$)
												NewTestRunId     = Test_Run_Services('CreateTestRunRecord', TestRunType, 'R', Reactor, PSNo, RDSNo,Username ,  TestWaferLots, TestWaferLotQtys)
												If Error_Services('NoError') then
													Continue     = True$
												end else
													ErrMsg = Error_Services('GetMessage')
													Continue     = False$
												end
												If Continue then
													*if RDSNo EQ 1006721 then debug
													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) 
												end
												
											end else
												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)
											end
											
										Case CurrStage _EQC 'LOAD'
											Action          = 'LOAD'
											If TestWaferLots NE '' then
												Continue         = False$
												ToolID           = ScansRow<SCANS.TOOL_ID$>
												Reactor          = ToolID[-1, 'BR']
												Username         = ScansRow<SCANS.EMPLOYEE_ID$>
												TestWaferLotQtys = ScansRow<SCANS.TW_LOT_QTY$>
												TestRunType = ScansRow<SCANS.TEST_RUN_TYPE_ID$>
												PSNo             = Database_Services('ReadDataColumn', 'RDS', RDSNo, RDS_PROD_SPEC_ID$, True$, 0, False$)
												NewTestRunId     = Test_Run_Services('CreateTestRunRecord', TestRunType, 'R', Reactor, PSNo, RDSNo,Username ,  TestWaferLots, TestWaferLotQtys)
												If Error_Services('NoError') then
													Continue     = True$
												end else
													ErrMsg = Error_Services('GetMessage')
													Continue     = False$
												end
												If Continue then
													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)
												end 
											end else
												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) 
											end
											
											// Check if LOAD stage is ready to sign
											
										Case CurrStage _EQC 'UNLOAD'
											Action = 'UNLOAD'
											Continue = True$
											If TestWaferLots NE '' then
												ToolID           = ScansRow<SCANS.TOOL_ID$>
												Reactor          = ToolID[-1, 'BR']
												Username         = ScansRow<SCANS.EMPLOYEE_ID$>
												TestWaferLotQtys = ScansRow<SCANS.TW_LOT_QTY$>
												TestRunType = ScansRow<SCANS.TEST_RUN_TYPE_ID$>
												PSNo             = Xlate('RDS', RDSNo, RDS_PROD_SPEC_ID$, True$, 'X')
												NewTestRunId     = Test_Run_Services('CreateTestRunRecord', TestRunType, 'R', Reactor, PSNo, RDSNo,Username ,  TestWaferLots, TestWaferLotQtys)
												If Error_Services('HasError') then
													ErrMsg = Error_Services('GetMessage')
													Continue = False$
												end
											end	
											
											If Continue EQ True$ then
												// 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)
											end
										Case CurrStage _EQC 'COMP'
											Action = 'COMP'
											ErrMsg = '(':CassetteID:") Cassette has already been FQA'd."
										Case Otherwise$
											Action = CurrStage
											ErrMsg = '(':CassetteID:') The ':CurrStage:' is not currently supported by the barcode application.'
									End Case    
								Case CassetteID EQ '' AND TestWaferLots NE ''
									//Tool scan with ONLY test wafers being logged.
									ToolID       = ScansRow<SCANS.TOOL_ID$>
									Reactor      = ToolID[-1, 'BR']
									Username     = ScansRow<SCANS.EMPLOYEE_ID$>
									TestWaferLotQtys = ScansRow<SCANS.TW_LOT_QTY$>
									NewTestRunId = Test_Run_Services('CreateTestRunRecord', 3, 'R', Reactor, '', '',Username ,  TestWaferLots, TestWaferLotQtys)
									If Error_Services('NoError') then
										If NewTestRunId NE '' then
											Result = 'Test run logged successfully'
										end else
											ErrMsg = 'Error while logging creating test run.'
										end
									end else
										ErrMsg = Error_Services('GetMessage')
									end
								Case Otherwise$
									//null
							End Case						           
					End Case
				end
				
				If ErrMsg EQ '' then
					ScansRow<SCANS.NOT_ACCEPTABLE_REASON$>  = 'This scan has already been accepted.'
				end else 
					ScansRow<SCANS.NOT_ACCEPTABLE_REASON$>  = ErrMsg                            
				end
				ScansRow<SCANS.ACCEPTABLE$> = False$
				If Assigned(Result) then ScansRow<SCANS.RESULT$> = Result
				
				Scan_Services('SetScansRow', ScanID, ScansRow)   
			end else
				ErrMsg = 'The accepted.status field is missing from the JSON object in the ':Service:' service.'
			end
			SRP_JSON(hBody, 'Release')
		end else
			ErrMsg = 'Unable to parse the JSON scan resource in the ':Service:' service.'
		end
	end else
		ErrMsg = 'ScanID or ScanJSON argument was missing in the ' : Service : ' service.'
	end
	
	EndTick = GetTickCount()
    Mona_Services('QueueLatencyAndCountMetrics', MonaResource, MetricName, StartTick, EndTick)
    
    If ErrMsg NE '' then Error_Services('Add', ErrMsg)
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)
	StartTick = GetTickCount()
    MetricName = 'ConvertMVScanToJSON'
	
	jsonScan    = ''
	
	mvScanChanged = False$
	
	ErrMsg = ''
	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')
					SRP_JSON(objScan, 'SetValue', 'testRunTypeId', {TEST_RUN_TYPE_ID}, 'Integer')
					SRP_JSON(objScan, 'SetValue', 'testRunTypeName', XLate('TEST_RUN_TYPE', {TEST_RUN_TYPE_ID}, TEST_RUN_TYPE_RUN_TYPE$, 'X'), 'String')
					
					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
				// TestWafer Lot Object.
				If SRP_JSON(hTWLotArray, 'New', 'Array') then
					Abort         = False$
					TestWaferLotsChanged = {TW_LOT_CHANGED}
					TW_Lot_Json = {TW_LOT_JSON}
					ParsingResult = ''
					TestWaferLots = {TW_LOT_ID}
					If TestWaferLots NE '' then
						If TestWaferLotsChanged NE True$ and TW_Lot_Json NE '' then
							If TW_Lot_Json NE '' then
								ParsingResult = SRP_JSON(TWLotJSON, 'Parse', TW_Lot_JSON)
								If ParsingResult EQ '' then
									SRP_JSON(objJSONScan, 'Set', 'testWaferLots', TWLotJSON)
									SRP_JSON(TWLotJSON, 'Release')
								end
							end
						end
						
						If TW_Lot_Json EQ '' or TestWaferLotsChanged EQ True$ or ParsingResult NE '' then
							for each TestWaferLotId in TestWaferLots using @VM setting twPos
								thisTWLotJson = ''
								If SRP_JSON(thisTWLotJson, 'New', 'Object') then
									SRP_JSON(thisTWLotJson, 'SetValue', 'testWaferLotId', {TW_LOT_ID}<1, twPos>)
									SRP_JSON(thisTWLotJson, 'SetValue', 'quantity', {TW_LOT_QTY}<1, twPos>, 'Integer')
									SRP_JSON(hTWLotArray, 'Add', thisTWLotJson)
									SRP_JSON(thisTWLotJson, 'Release')
								end	
							Next TestWaferLotId
							
							TW_Lot_Json = SRP_JSON(hTWLotArray, 'Stringify', 'Fast')
							mvScan<SCANS.TW_LOT_JSON$> = TW_Lot_Json
							mvScan<SCANS.TW_LOT_CHANGED$> = False$
							mvScanChanged = True$
							
							SRP_JSON(objJSONScan, 'Set', 'testWaferLots', hTWLotArray)
						end
					end
					SRP_JSON(hTWLotArray, 'Release')
				end
				SRP_JSON(objScan, 'SetValue', 'testRunTypeId', {TEST_RUN_TYPE_ID}, 'Integer')
				
				// TestRun Type Option Array
				twRunTypeListCacheKey$ = 'TW_RUN_TYPE_OPTIONS'
				jsonField$ = 1
				lastTickField$ = 2
				oneHourTicks$ = 3600000
				
				TestRunTypeOptionsJson = Xlate('APP_INFO', twRunTypeListCacheKey$, jsonField$, 'X')
				LastTick = Xlate('APP_INFO', 'TW_RUN_TYPE_OPTIONS', lastTickField$, 'X')
				TickDiff = oneHourTicks$ + oneHourTicks$
				ParsingResponse = ''
				If TestRunTypeOptionsJson NE '' and LastTick NE '' and Num(LastTick) then
					TickDiff = GetTickCount() - LastTick
				
					If TestRunTypeOptionsJson NE '' and TickDiff LT oneHourTicks$ then
						ParsingResponse = SRP_JSON(hTRTypeOpts, 'Parse', TestRunTypeOptionsJson)
						If ParsingResponse EQ '' then
							SRP_JSON(objJSONScan, 'Set', 'testRunTypeOptions', hTRTypeOpts)
							SRP_JSON(hTRTypeOpts, 'Release')
						end
					end
				end
				
				
				If LastTick EQ '' or Num(LastTick) EQ False$ or TestRunTypeOptionsJson EQ '' or TickDiff GE oneHourTicks$ or ParsingResponse NE '' then
					If SRP_JSON(hTestRunTypeArray, 'New', 'Array') then
						TestRunTypeList = Test_Run_Services('GetAllTestRunTypes')
						For each TestRunTypeId in TestRunTypeList<1> using @VM setting fPos
							hTestRunTypeObj = ''
							If SRP_JSON(hTestRunTypeObj, 'New', 'Object') then
								SRP_JSON(hTestRunTypeObj, 'SetValue', 'testRunTypeId', TestRunTypeId, 'Number')
								SRP_JSON(hTestRunTypeObj, 'SetValue', 'testRunTypeName', TestRunTypeList<2, fPos>, 'String')
								SRP_JSON(hTestRunTypeArray, 'Add', hTestRunTypeObj)
								SRP_JSON(hTestRunTypeObj, 'Release')
							end else
								Abort = True$
							end
						Until Abort
						Next TestRunTypeId
						If Abort EQ False$ then
							SRP_JSON(objJSONScan, 'Set', 'testRunTypeOptions', hTestRunTypeArray)
							TRTypeListJson = SRP_JSON(hTestRunTypeArray, 'Stringify', 'Fast')
							LastTick = GetTickCount()
							
							twRunTypeOptionsRec = ''
							twRunTypeOptionsRec<jsonField$> = TRTypeListJson
							twRunTypeOptionsRec<lastTickField$> = LastTick
							
							Database_Services('WriteDataRow', 'APP_INFO', twRunTypeListCacheKey$, twRunTypeOptionsRec, True$, False$, False$)
						end
					end
					SRP_JSON(hTestRunTypeArray, 'Release')
				end
				// Employee object
				EmployeeChanged = {EMPLOYEE_CHANGED}
				EmployeeJson = {EMPLOYEE_JSON}
				ParsingResult = ''
				If EmployeeChanged NE True$ and EmployeeJson NE '' then
					ParsingResult = SRP_JSON(hEmployeeJson, 'Parse', EmployeeJson)
					If ParsingResult EQ '' then
						SRP_JSON(objJSONScan, 'Set', 'employee', hEmployeeJson)
						SRP_JSON(hEmployeeJson, 'Release')
					end
				end
				
				If EmployeeJson EQ '' or EmployeeChanged EQ True$ or ParsingResult NE '' then
					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(objEmployee, 'SetValue', 'isAuthorizedToOverrideROTR', @Record<SCANS.IS_AUTHORIZED_TO_OVERRIDE_ROTR$>, 'Boolean')
						SRP_JSON(objJSONScan, 'Set', 'employee', objEmployee)
						
						EmployeeJson = SRP_JSON(objEmployee, 'Stringify', 'Fast')
						mvScan<SCANS.EMPLOYEE_JSON$> = EmployeeJson
						mvScan<SCANS.EMPLOYEE_CHANGED$> = False$
						mvScanChanged = True$
						
						SRP_JSON(objEmployee, 'Release')
					end
				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 'VER') ) ) |
					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')
							If CurrStage EQ 'VER' then
								VerSupplID = Supplement_Services('UnacknowledgedSupplementCheck', 'RDS', RDSNo, 'VER')
								If VerSupplID NE False$ then
									SupplVerInstAckReq = True$
								end else
									SupplVerInstAckReq = False$
								end
							end else
								SupplVerInstAckReq = False$
							end
							
							If CurrStage EQ 'VER' or CurrStage EQ 'LOAD' then
								LoadSupplID = Supplement_Services('UnacknowledgedSupplementCheck', 'RDS', RDSNo, 'LOAD')
								If LoadSupplID NE False$ then
									SupplLoadInstAckReq = True$
								end else
									SupplLoadInstAckReq = False$
								end
							end else
								SupplLoadInstAckReq = False$
							end
							
							If CurrStage EQ 'UNLOAD' then
								UnloadSupplID = Supplement_Services('UnacknowledgedSupplementCheck', 'RDS', RDSNo, 'UNLOAD')
								If UnloadSupplID NE False$ then
									SupplUnloadInstAckReq = True$
								end else
									SupplUnloadInstAckReq = False$
								end
							end else
								SupplUnloadInstAckReq = False$
							end
							
							If CurrStage EQ 'POST' then
								PostSupplID = Supplement_Services('UnacknowledgedSupplementCheck', 'RDS', RDSNo, 'POST')
								If PostSupplID NE False$ then
									SupplPostInstAckReq = True$
								end else
									SupplPostInstAckReq = False$
								end
							end else
								SupplPostInstAckReq = False$
							end
							
							@DICT   = Database_Services('GetTableHandle', 'DICT.SCANS')
							@ID     = ScanID
							@RECORD = mvScan
							
							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')
							
							*							SchedWfrCnt    = Xlate('SCANS', ScanID, 'SCHEDULER_WAFER_COUNT', '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')
							
							
							If supplLoadInstAckReq EQ TRUE$ then
								supplLoadInstAck = Xlate('SUPPLEMENTS', LoadSupplID, SUPPLEMENTS_SUPPL_ACK$, 'X', '')
								supplLoadInst = Xlate('SUPPLEMENTS', LoadSupplID, SUPPLEMENTS_SUPPL_TEXT$, 'X', '')
							end else
								supplLoadInstAck = ''
								supplLoadInst = ''
							end
							SRP_JSON(objRDS, 'SetValue', 'supplLoadInstAckReq'      , supplLoadInstAckReq               , 'Boolean')
							SRP_JSON(objRDS, 'SetValue', 'supplLoadInstAck'         , supplLoadInstAck        , 'Boolean')
							SRP_JSON(objRDS, 'SetValue', 'supplLoadInst'            , supplLoadInst       , 'String')
							
							If supplVerInstAckReq EQ TRUE$ then
								supplVerInstAck = Xlate('SUPPLEMENTS', VerSupplID, SUPPLEMENTS_SUPPL_ACK$, 'X', '')
								supplVerInst = Xlate('SUPPLEMENTS', VerSupplID, SUPPLEMENTS_SUPPL_TEXT$, 'X', '')
							end else
								supplVerInstAck = ''
								supplVerInst = ''
							end
							SRP_JSON(objRDS, 'SetValue', 'supplVerInstAckReq'      , supplVerInstAckReq               , 'Boolean')
							SRP_JSON(objRDS, 'SetValue', 'supplVerInstAck'         , supplVerInstAck        , 'Boolean')
							SRP_JSON(objRDS, 'SetValue', 'supplVerInst'            , supplVerInst       , 'String')
							
							
							If supplUnloadInstAckReq EQ TRUE$ then
								supplUnloadInstAck = Xlate('SUPPLEMENTS', UnloadSupplID, SUPPLEMENTS_SUPPL_ACK$, 'X', '')
								supplUnloadInst = Xlate('SUPPLEMENTS', UnloadSupplID, SUPPLEMENTS_SUPPL_TEXT$, 'X', '')
							end else
								supplUnloadInstAck = ''
								supplUnloadInst = ''
							end
							SRP_JSON(objRDS, 'SetValue', 'supplUnloadInstAckReq'      , supplUnloadInstAckReq               , 'Boolean')
							SRP_JSON(objRDS, 'SetValue', 'supplUnloadInstAck'         , supplUnloadInstAck       , 'Boolean')
							SRP_JSON(objRDS, 'SetValue', 'supplUnloadInst'            , supplUnloadInst       , 'String')
							
							If supplPostInstAckReq EQ TRUE$ then
								supplPostInstAck = Xlate('SUPPLEMENTS', PostSupplID, SUPPLEMENTS_SUPPL_ACK$, 'X', '')
								supplPostInst = Xlate('SUPPLEMENTS', PostSupplID, SUPPLEMENTS_SUPPL_TEXT$, 'X', '')
							end else
								supplPostInstAck = ''
								supplPostInst    = ''
							end
							SRP_JSON(objRDS, 'SetValue', 'supplPostInstAckReq'      , supplPostInstAckReq               , 'Boolean')
							SRP_JSON(objRDS, 'SetValue', 'supplPostInstAck'         , supplPostInstAck        , 'Boolean')
							SRP_JSON(objRDS, 'SetValue', 'supplPostInst'            , supplPostInst       , '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
				
				jsonScan    = SRP_JSON(objJSONScan, 'Stringify', 'Fast')
				SRP_JSON(objJSONScan, 'Release')
				
				If mvScanChanged EQ True$ then
					Database_Services('WriteDataRow', 'SCANS', {SCAN_ID}, mvScan, True$, False$, False$)
				end
			end else
				ErrMsg = 'Unable to create JSON representation in the ' : Service : ' service.'
			end
		end
	end else
		ErrMsg = 'ScanID argument was missing in the ' : Service : ' service.'
	end
	
	Response    = jsonScan
	
	EndTick = GetTickCount()
    Mona_Services('QueueLatencyAndCountMetrics', MonaResource, MetricName, StartTick, EndTick)
    
    If ErrMsg NE '' then Error_Services('Add', ErrMsg)
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')
			mvScan<SCANS.TEST_RUN_TYPE_ID$>      = SRP_JSON(objJSONScan, 'GetValue', 'scan.testRunTypId')
			mvScan<SCANS.TEST_RUN_TYPE_NAME$>    = SRP_JSON(objJSONScan, 'GetValue', 'scan.testRunTypeName')
			
			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


