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

    Name        :   RDS_Services

    Description :   Handler program for all RDS services.

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

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

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

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

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

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

    Metadata    :

    History     :   (Date, Initials, Notes)
        05/14/18    djs     Original programmer.
        09/18/18    djs     Added the IsEpiPro service.
        08/23/21	dpc		Added validation routine IsPackaged
        08/28/23	djm		Added view/add comment functions.
        06/13/24	djm		Add GetCurrentStatus service.
        07/18/24	djm		Update AddComment to use Obj_Post_Log to reduce lag.

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

$Insert APP_INSERTS
$Insert MSG_EQUATES
$Insert RTI_DEBUG_COMMON
$Insert SRPMail_Inserts
$Insert LOGICAL
$Insert SERVICE_SETUP
$Insert RDS_EQUATES
$Insert PROD_SPEC_EQUATES
$Insert RDS_LAYER_EQUATES
$Insert PRS_LAYER_EQUATES
$Insert RDS_TEST_EQUATES
$Insert TOOL_CLASS_EQUATES
$Insert TEST_POINT_MAP_EQUATES
$Insert CLEAN_INSP_EQUATES
$Insert REACT_RUN_EQUATES
$Insert REACTOR_EQUATES
$Insert WO_MAT_QA_EQUATES
$Insert PRS_STAGE_EQUATES
$Insert PRS_PROP_EQUATES
$Insert WO_LOG_EQUATES
$Insert WM_OUT_EQUATES
$Insert REACT_UTIL_EQU
$Insert WO_MAT_EQUATES
$Insert RLIST_EQUATES
$Insert REACT_MODE_EQUATES
$Insert LSL_USERS_EQUATES
$Insert TOOL_EQUATES
$Insert POPUP_EQUATES
$Insert NOTIFICATION_EQUATES
$Insert DICT_EQUATES
$Insert COMPANY_EQUATES
$Insert RETURN_TO_FAB_LOTS_EQUATES
$Insert TEST_RUN_EQUATES

EQU COL$LSID			TO 1
EQU COL$RECIPE			TO 2
EQU COL$RECIPE_NAME		TO 3
EQU COL$PARAMS			TO 4
EQU COL$PARMS_OUT		TO 5
EQU COL$BLANK_COL		TO 6
EQU COL$MET_NO			TO 7
EQU COL$THICK_AVG		TO 8
EQU COL$RRHO_AVG		TO 9
EQU COL$MET_NO_Z1		TO 10
EQU COL$THICK_AVG_Z1	TO 11
EQU COL$RRHO_AVG_Z1		TO 12
EQU COL$MET_NO_Z2		TO 13
EQU COL$THICK_AVG_Z2	TO 14
EQU COL$RRHO_AVG_Z2		TO 15
EQU COL$MET_OUT			TO 16
EQU COL$MET_OUT_Z1		TO 17
EQU COL$MET_OUT_Z2		TO 18
EQU COL$MET_TWSIG		TO 19
EQU COL$MET_TWSIG_Z1	TO 20
EQU COL$MET_TWSIG_Z2	TO 21

EQU COMMA$ to ','

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

Declare subroutine  SRP_Stopwatch, Error_Services, obj_Tables, Metrology_Services, obj_RDS_Test, SRP_JSON, Logging_Services
Declare subroutine  RTI_Set_Debugger, Database_Services, Btree.Extract, Extract_SI_Keys, Obj_WO_Mat, Obj_WO_Mat_Log
Declare subroutine  Dialog_Box, Obj_Notes, RList, Set_Status, Errmsg, Obj_React_Status	, Reactor_Services
Declare subroutine  Rds_Services, Obj_Post_Log, Mona_Services
Declare function    SRP_Sort_Array, Metrology_Services, obj_RDS_Test, obj_Test_Point_Map, Database_Services
Declare function    Work_Order_Services, SRP_JSON, Logging_Services, Environment_Services, SRP_Trim, Error_Services
Declare function    Memberof, Obj_WO_Mat, Obj_WO_Mat_Log, SRP_Array, DateTime, Dialog_Box, obj_Prod_Spec
Declare function    RDS_Services, RTI_DistinctList, GetTickCount, Clean_Insp_Services, WO_Mat_QA_Services
Declare function    PSN_Services, Rds_Layer_Services, SRP_Rotate_Array, Qa_Services, SRP_Date, SRP_Time, SRP_Datetime
Declare function	Reactor_Services, Signature_Services, obj_rds, Test_Run_Services, Return_To_Fab_Services

LogPath       = Environment_Services('GetApplicationRootPath') : '\LogFiles\RDS'
LogDate       = Oconv(Date(), 'D4/')
LogTime       = Oconv(Time(), 'MTS')
LogFileName   = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' Ctrl Performance Log.csv'
Headers       = 'Logging DTM':@FM:'Ctrl':@FM:'Load/Calculate Time'
objCtrlLog    = 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_RDSSERVICES'
end else
	MonaResource = 'GRP_OPENINSIGHT_MES_OP_FE_DEV_RDSSERVICES'
end

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

Return Response else ''


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Service Parameter Options
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Options BOOLEAN     = True$, False$
Options STAGES      = 'PRE', 'FWI', 'LWI', 'QA', 'LOAD', 'UNLOAD'



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

//----------------------------------------------------------------------------------------------------------------------
// GetCurrentStage
//
// RDSNo. - [Required]
//
// Returns the current stage of a lot. Replaces CURR_STAGE calculated column.
//----------------------------------------------------------------------------------------------------------------------
Service GetCurrentStage(RDSNo)

	If RDSNo NE '' then
		RDSCheck   = Xlate('RDS', RDSNo, '', 'X', '')
		If RDSCheck NE '' then
			CurrStage  = ''
			WOMatKey   = Xlate('RDS', RDSNo, 'WO_MAT_KEY', 'X')
			SigProfile = Signature_Services('GetSigProfile', WOMatKey, FALSE$, RDSNo)
			Stages     = SigProfile<1>
			Signatures = SigProfile<2>
			SigCount   = DCount(Signatures, @VM)
			StageCount = DCount(Stages, @VM)
			Swap @VM with @FM in Stages
			Swap @VM with @FM in Signatures
			For I = 1 to StageCount
				If Signatures<I> EQ '' then
					CurrStage = Stages<I>
				end
			Until Signatures<I> EQ ''
			Next I
		
			If CurrStage EQ '' then CurrStage = 'COMP'
			Response = CurrStage
		end else
			ErrorMessage = 'Invalid RDSNo.'
			Error_Services('Add', ErrorMessage)
		end
	end else
		ErrorMessage = 'This service requires an RDSNo to be passed in.'
		Error_Services('Add', ErrorMessage)
	end

End Service

//----------------------------------------------------------------------------------------------------------------------
// GetComments
//
// RDSNo. - [Required]
//
// Returns a delimited array of all COMMENT, COMMENT_USER, and COMMENT_DATE OR
// EPP_COMMENT, EPP_COMMENT_USER, and EPP_COMMENT_DATE associated with an RDS record. Note: Dates are returned Oconv'd.
//----------------------------------------------------------------------------------------------------------------------
Service GetComments(RDSNo)
	
	CommentArray = ''
	RDSRow          = Database_Services('ReadDataRow', 'RDS', RDSNo)
	
	CommentDates = Oconv(RDSRow<RDS_COMMENT_DATE$>, 'DT')
	CommentUsers = RDSRow<RDS_COMMENT_USER$>
	Comments = RDSRow<RDS_COMMENT_NOTE$>
	
	CommentList = CommentDates :@FM: CommentUsers :@FM: Comments
	CommentArray = SRP_Rotate_Array(CommentList)
	Response = CommentArray
	
End Service


//----------------------------------------------------------------------------------------------------------------------
// AddComment
//
// RDSNo. - [Required]
// Comment - [Required]
// UsernameOpt - [Optional]
//
// Adds a COMMENT, COMMENT_USER, and COMMENT_DATE OR an EPP_COMMENT, EPP_COMMENT_USER, and EPP_COMMENT_DATE
// to an RDS record.
//----------------------------------------------------------------------------------------------------------------------
Service AddComment(RDSNo, Comment, UsernameOpt)
	StartTick = GetTickCount()
    MetricName = 'AddComment'
	
	RDSRow   = Database_Services('ReadDataRow', 'RDS', RDSNo)
	Username = @USER4
	If Assigned(UsernameOpt) then
		If UsernameOpt NE '' then
			Username = UsernameOpt
		end
	end
	CommentTime = Datetime()
	
	Fields  = RDS_COMMENT_DATE$:@VM		; Values  = CommentTime:@VM
	Fields := RDS_COMMENT_USER$:@VM		; Values := UserName:@VM
	Fields := RDS_COMMENT_NOTE$ 		; Values := Comment
	
	
	oblParms  = 'RDS':@RM
	oblParms := RDSNo:@RM
	oblParms := Fields:@RM
	oblParms := Values:@RM
	oblParms := "TOP" :@VM: "TOP" :@VM: "TOP"
	obj_Post_Log('Create',oblParms)
	
	EndTick = GetTickCount()
    Mona_Services('QueueLatencyAndCountMetrics', MonaResource, MetricName, StartTick, EndTick)
End Service


//----------------------------------------------------------------------------------------------------------------------
// GetFinalQAStatus
//
// RDSNo. - [Required]
//
// Returns the boolean value of the Final QA status of a given RDS record. Returns True$ if
// the Final QA step is signed and False$ if it is not signed.
//----------------------------------------------------------------------------------------------------------------------
Service GetFinalQAStatus(RDSNo)
	
	FinalQA         = False$
	RDSRow          = Database_Services('ReadDataRow', 'RDS', RDSNo)
	WorkOrderNo     = RDSRow<RDS_WO$>
	WorkOrderRow    = Database_Services('ReadDataRow', 'WO_LOG', WorkOrderNo)
	ReactType       = WorkOrderRow<WO_LOG_REACT_TYPE$>
	// Check to see if the Final QA signature is in place.
	If ReactType EQ 'EPP' then
		CassetteNos = RDSRow<RDS_OUT_CASS_NO$>
		For Each CassetteNo in CassetteNos using @VM
			WMOutKey    = RDSRow<RDS_WO_STEP_KEY$> : '*' : CassetteNo
			WMOutRow    = Database_Services('ReadDataRow', 'WM_OUT', WMOutKey)
			If WMOutRow<WM_OUT_SUP_VER_SIG$> NE '' then
				FinalQA = true$
			end
		Until FinalQA
		Next CassetteNo
	end else
		If RDSRow<RDS_SUP_VER_SIG$> NE '' then
			FinalQA = True$
		end
	end
	Response = FinalQA
	
End Service

Service IsPackaged(RDSNo, RDSType)
	
	isPackaged  = False$
	//ReactType   = XLATE('RDS', RDSNo, 'REACTOR_TYPE', '')
	// Check to see if the Final QA signature is in place.
	If RDSType EQ 'EPP' then
		Convert '.' TO '*' in RDSNo
		WMOutKey    = Xlate('WM_OUT', RDSNo, 'WO_MAT_KEY', 'X')
		WoMatAction = Database_Services('ReadDataColumn', 'WO_MAT',WMOutKey, 8)
		isPackaged = Index(WoMatAction, 'PACK', 1) > 0
	end else if RDSType EQ 'GAN' then
		Convert '.' TO '*' in RDSNo
		WoMatAction = Database_Services('ReadDataColumn', 'WO_MAT',RDSNo, 8)
		isPackaged = Index(WoMatAction, 'PACK', 1) > 0
	end else
		RDSRow      = Database_Services('ReadDataRow', 'RDS', RDSNo)
		WoNo		= RDSRow<RDS_WO$>
		WoCassNo	= RDSRow<RDS_CASS_NO$>
		WoMatKey	= WoNo:'*':WoCassNo
		WoMatAction = Database_Services('ReadDataColumn', 'WO_MAT',WoMatKey, 8)
		isPackaged = Index(WoMatAction, 'PACK', 1) > 0
	end
	Response = isPackaged
	
End Service

Service GetHoldStatus(RDSNo, RDSType)
	Response = False$
	If Error_Services('NoError') then
		IF RDSType EQ 'EPP' then
			Convert '.' to '*' in RDSNo
			WOMatKey    = Xlate('WM_OUT', RDSNo, 'WO_MAT_KEY', 'X')
		end else iF RDSType EQ 'GAN' then
			Convert '.' to '*' in RDSNo
			WOMatKey    = RDSNo
		end else
			RDSRec = Database_Services('ReadDataRow', 'RDS', RDSNo)
			WONo   = RDSRec<RDS_WO$>
			CassNo = RDSRec<RDS_CASS_NO$>
			WOMatKey = WONo:'*':CassNo
		end
		
		If WOMatKey NE '' then
			WOMatCurrStatus = obj_WO_Mat('CurrStatus', WOMatKey)
			If WOMatCurrStatus EQ 'HOLD' then Response = True$
		end else
			ErrorMessage = 'Null work order or cassette number in ':Service' for RDS ':RDSNo:'.'
			Error_Services('Add', ErrorMessage)
		end
	end
	
End Service


//----------------------------------------------------------------------------------------------------------------------
// GetRDSList
//
// WorkOrderNo. - [Required]
// KeysOnly.    - [Optional]
//
// Returns a list of RDS records within a supplied Work Order.
// Rows are @FM delimted while columns are @VM delimited.
//----------------------------------------------------------------------------------------------------------------------
Service GetRDSList(WorkOrderNo, KeysOnly=BOOLEAN)
	
	RDSList   = ''
	
	If WorkOrderNo NE '' then
		rv              = Set_Status(0)
		RDSKeys         = ''
		extract_si_keys('RDS', 'WO', WorkOrderNo, RDSKeys)
		StatusCode      = ''
		If Get_Status(StatusCode) then
			Error_Services('Add', 'Error calling Extract_SI_Keys in the ' : Service : ' service. StatusCode: ' : StatusCode)
		end else
			NumRDS = DCount(RDSKeys, @VM)
			@DICT = Database_Services('GetTableHandle', 'DICT.' : 'RDS')
			If Error_Services('NoError') then
				For Each @ID in RDSKeys using @VM
					@RECORD = Database_Services('ReadDataRow', 'RDS', @ID)
					If Error_Services('NoError') then
						RDSDet    = {ABBREV_OR_CO_NAME} : @VM
						RDSDet   := {WO} : @VM
						RDSDet   := {WO_STEP} : @VM
						RDSDet   := {RUN_ORDER_NUM} : @VM
						RDSDet   := @ID : @VM
						RDSDet   := {REACTOR} : @VM
						RDSDet   := {REACT_IDLE_TIME} : @VM
						RDSDet   := {HGCV_TEST} : @VM
						RDSDet   := {DATE_IN} : @VM
						RDSDet   := {TIME_IN} : @VM
						RDSDet   := {DATE_OUT} : @VM
						RDSDet   := {TIME_OUT} : @VM
						RDSDet   := {ELAPSED_HOURS} : @VM
						RDSDet   := {PO} : @VM
						RDSDet   := {PART_NUM} : @VM
						RDSDet   := {LOT_NUM} : @VM
						RDSDet   := {COMB_STATUS} : @VM
						RDSDet   := {NCR_SUM} : @VM
						RDSDet   := {HOLD_STATUS} : @VM
						RDSDet   := {SUPPL_INST}
						If KeysOnly EQ True$ then
							RDSList   = Insert(RDSList, -1, 0, 0, @ID)
						end else
							RDSList   = Insert(RDSList, -1, 0, 0, RDSDet)
						end
					end else
						Error_Services('Add', 'Error reading RDS Record ' : @ID : ' in the ' : Service : ' service.')
					end
				Next @ID
			end else
				Error_Services('Add', 'Error opening RDS dictionary in the ' : Service : ' service.')
			end
		end
	end else
		Error_Services('Add', 'WorkOrderNo argument was missing from the ' : Service : ' service.')
	end
	
	Response    = RDSList
	
end service


//----------------------------------------------------------------------------------------------------------------------
// GetRDSData
//
// WorkOrderNo. - [Required]
// KeysOnly.    - [Optional]
//
// Returns a list of RDS records within a supplied Work Order.
// Rows are @FM delimted while columns are @VM delimited.
//----------------------------------------------------------------------------------------------------------------------
Service GetRDSData(WorkOrderNo, Columns, ShowGasGauge, RDSOverrideList)
	
	RDSList   = ''

	If ( (WorkOrderNo NE '') or (RDSOverrideList NE '') ) then
		If ShowGasGauge NE True$ then ShowGasGauge = False$
		rv              = Set_Status(0)
		RDSKeys         = ''
		
		If WorkOrderNo NE '' then
			Extract_Si_Keys('RDS', 'WO', WorkOrderNo, RDSKeys)
		end else
			Swap @FM with @VM in RDSOverrideList
			RDSKeys = RDSOverrideList
		end
		
		StatusCode      = ''
		If Get_Status(StatusCode) then
			Error_Services('Add', 'Error calling Extract_SI_Keys in the ' : Service : ' service. StatusCode: ' : StatusCode)
		end else
			If RDSKeys NE '' then
				NumRDS = DCount(RDSKeys, @VM)
				
				If ShowGasGauge then
					Def = ""
					Def<MCAPTION$>   = "Loading Run Data Sheets..."
					Def<MTYPE$>      = "G"
					Def<MEXTENT$>    = NumRDS
					Def<MTEXTWIDTH$> = 400
					Def<MCOL$>       = -2        ;* message h-pos in pixels, or -2 (center screen, the default), -1 (center parent)
					Def<MROW$>       = -2        ;* message v-pos in pixels
					MsgUp  = Msg(@Window, Def)
				end
				
				@DICT      = Database_Services('GetTableHandle', 'DICT.' : 'RDS')
				If Error_Services('NoError') then
					For each @ID in RDSKeys using @VM setting fPos
						@RECORD = Database_Services('ReadDataRow', 'RDS', @ID)
						If Error_Services('NoError') then
							For each Column in Columns using @VM setting vPos
								LoadTimeStart    = GetTickCount()
								Begin Case
									Case Column EQ 'HOLD_STATUS'
										HoldStatus          = Calculate(Column)
										If HoldStatus EQ 'No' then HoldStatus = 'Off Hold'
										RDSList<fPos, vPos> = HoldStatus
									Case Column EQ 'ELAPSED_HOURS'
										Val                 = OConv(Calculate(Column), 'MD2L')
										Hours               = Field(Val, '.', 1)
										Minutes             = '0.':Field(Val, '.', 2)
										Minutes             = 60 * Minutes
										Minutes             = Minutes[1, 'F.']
										Minutes             = Fmt(Minutes, 'R(0)#2')
										RDSList<fPos, vPos> = Hours:':':Minutes
									Case Otherwise$
										RDSList<fPos, vPos> = Calculate(Column)
									
								End Case
							Next Column
						end else
							Error_Services('Add', 'Error reading RDS Record ' : @ID : ' in the ' : Service : ' service.')
						end
						* update the gauge
						If ShowGasGauge then Msg(@Window, MsgUp, fPos, MSGINSTUPDATE$)
					Next @ID       
				end else
					Error_Services('Add', 'Error opening RDS dictionary in the ' : Service : ' service.')
				end
				If ShowGasGauge then Msg(@Window, MsgUp)         ;* take down the gauge
			end
		end
	end else
		Error_Services('Add', 'WorkOrderNo or RDSOverrideList argument was missing from the ' : Service : ' service.')
	end
	//Sort based on cassette number
	Locate 'RUN_ORDER_NUM' in Columns using @VM setting ColNoCassNo else ColNoCassNo = 1
	RDSList = SRP_Sort_Array(RDSList, 'AR' : ColNoCassNo, True$, @FM)
	Response    = RDSList
	
end service


//----------------------------------------------------------------------------------------------------------------------
// SetSupplement
//
// SupplementList.  - [Required]
// Username.        - [Optional]
//
// Accepts a list with the following structure:
// <1>       Row     Text
//  <1,1>    Column  Text    RDSNo
//  <1,2>    Column  Text    Supplement Instructions
//----------------------------------------------------------------------------------------------------------------------
Service SetSupplement(SupplementList, Username)
	RDSNotificationListAdd = ''
	RDSNotificationListRemove = ''
	Action = ''
	If SupplementList NE '' then
		@DICT = Database_Services('GetTableHandle', 'DICT.' : 'RDS')
		If Error_Services('NoError') then
			For Each Row in SupplementList using @FM
				ChangeDetected = False$
				@ID            = Row<1,1>
				SupplementInst = Row<1,2>
				@RECORD = Database_Services('ReadDataRow', 'RDS', @ID)
				If Error_Services('NoError') then
					If SupplementInst NE '' then                        
						If SupplementInst NE {SUPPL_INST} then
							// Supplement information has been added or updated.
							ChangeDetected       = True$
							Action = 'Add'
							{SUPPLEMENT}         = True$
							{SUPPL_INST}         = SupplementInst
							{SUPPL_ENTRY_DATE}   = date()
							{SUPPL_ENTRY_TIME}   = time()					
							If Username NE '' then
								{SUPPL_ENTRY_ID} = Username
							end else
								{SUPPL_ENTRY_ID} = @User4
							end
						end
					end else
						If SupplementInst NE {SUPPL_INST} then
							// Supplement information has been removed.
							ChangeDetected       = True$
							Action = 'Remove'
							{SUPPLEMENT}         = False$
							{SUPPL_INST}         = ''
							{SUPPL_ENTRY_DATE}   = ''
							{SUPPL_ENTRY_TIME}   = ''					
							{SUPPL_ENTRY_ID}     = ''
						end
					end					
					If ChangeDetected And Action EQ 'Add' then
						Database_Services('WriteDataRow', 'RDS', @ID, @RECORD)
						RDSNotificationListAdd<-1> = @ID 
					end
					If ChangeDetected And Action EQ 'Remove' then
						Database_Services('WriteDataRow', 'RDS', @ID, @RECORD)
						RDSNotificationListRemove<-1> = @ID
					end
				end else
					Error_Services('Add', 'Error reading RDS Record ' : @ID : ' in the ' : Service : ' service.')
				end
			Next Row
		end else
			Error_Services('Add', 'Error opening RDS dictionary in the ' : Service : ' service.')
		end
	end else
		Error_Services('Add', 'SupplementList argument was missing from the ' : Service : ' service.')
	end
	
	* //Now send an email notification
	IF RDSNotificationListAdd NE '' then
		Instructions = XLATE('RDS', RDSNotificationListAdd<1>, RDS_SUPPL_INST$, 'X')
		PSN = XLATE('RDS', RDSNotificationListAdd<1>, RDS_PROD_SPEC_ID$, 'X')
		WoNo = XLATE('RDS', RDSNotificationListAdd<1>, RDS_WO$, 'X')
		ReactorNo = XLATE('RDS', RDSNotificationListAdd<1>, RDS_REACTOR$, 'X');
		
		Message = 'This Supplement was created or updated by ':oconv( @user4, '[XLATE_CONV,LSL_USERS*FIRST_LAST]' ):' on ':OCONV( DATE(), 'D2/' ):' at ':OCONV( TIME(), 'MTH' ):'.' : CRLF$
		Message := 'WO# : ' : WoNo : CRLF$
		Message := 'PSN : ' : PSN : CRLF$
		Message := 'Reactor No : ' : ReactorNo : CRLF$
		//Message := 'Instructions : ' : Instructions
		Message := CRLF$
		Message := 'RDS #s : ' : CRLF$
		for each RDSNo in RDSNotificationListAdd using @FM
			Message := RDSNo : ' - ': XLATE('RDS', RDSNo, RDS_SUPPL_INST$, 'X') :CRLF$
		Next RDSNo
		
		Recipients		= XLATE('NOTIFICATION','SUPPLEMENTS',NOTIFICATION_USER_ID$,'X')      
		SendFrom		= 'System'       
		Subject		= 'New or Updated Supplement Created for WO# ': WoNo
		AttachWindow	= ''
		AttachKey		= ''
		SendToGroup		= ''
		
		Parms = Recipients:@RM:SendFrom:@RM:Subject:@RM:Message:@RM:AttachWindow:@RM:AttachKey:@RM:SendToGroup
		obj_Notes('Create',Parms)
	end
	IF RDSNotificationListRemove NE '' then
		
		Instructions = XLATE('RDS', RDSNotificationListRemove<1>, RDS_SUPPL_INST$, 'X')
		PSN = XLATE('RDS', RDSNotificationListRemove<1>, RDS_PROD_SPEC_ID$, 'X')
		WoNo = XLATE('RDS', RDSNotificationListRemove<1>, RDS_WO$, 'X')
		ReactorNo = XLATE('RDS', RDSNotificationListRemove<1>, RDS_REACTOR$, 'X');
		
		Message = 'This Supplement was removed by ':oconv( @user4, '[XLATE_CONV,LSL_USERS*FIRST_LAST]' ):' on ':OCONV( DATE(), 'D2/' ):' at ':OCONV( TIME(), 'MTH' ):'.' : CRLF$
		Message := 'WO# : ' : WoNo : CRLF$
		Message := 'PSN : ' : PSN : CRLF$
		Message := 'Reactor No : ' : ReactorNo : CRLF$
		//Message := 'Instructions : ' : Instructions
		Message := CRLF$
		Message := 'RDS #s : ' : CRLF$
		for each RDSNo in RDSNotificationListRemove using @FM
			Message := RDSNo :CRLF$
		Next RDSNo
		
		Recipients		= XLATE('NOTIFICATION','SUPPLEMENTS',NOTIFICATION_USER_ID$,'X')      
		SendFrom		= 'System'       
		Subject		= 'Supplement Removed for WO# ': WoNo
		AttachWindow	= ''
		AttachKey		= ''
		SendToGroup		= ''
		
		Parms = Recipients:@RM:SendFrom:@RM:Subject:@RM:Message:@RM:AttachWindow:@RM:AttachKey:@RM:SendToGroup
		obj_Notes('Create',Parms)
	end
end service


//----------------------------------------------------------------------------------------------------------------------
// IsEpiPro
//
// RDSNo. - [Required]
//
// Returns True$ if RDS is an EpiPro run, False$ otherwise.
//----------------------------------------------------------------------------------------------------------------------
Service IsEpiPro(RDSNo)
	
	IsEpiPro = False$
	ReactorKey  = Xlate('RDS', RDSNo, 'REACTOR', 'X')
	ReactorType = Xlate('REACTOR', ReactorKey, 'REACT_TYPE', 'X')
	If ReactorType _EQC 'EPP' or ReactorType _EQC 'P' or ReactorType _EQC 'EpiPro' then
		IsEpiPro = True$
	end
	Response = IsEpiPro
	
End Service


//----------------------------------------------------------------------------------------------------------------------
// IsEpiProTestRun
//
// RDSNo. - [Required]
//
// Returns True$ if RDS is an EpiPro test run, False$ otherwise.
//----------------------------------------------------------------------------------------------------------------------
Service IsEpiProTestRun(RDSNo)
	
	TestRun = True$
	If RDSNo NE '' then
		EpiPro   = RDS_Services('IsEpiPro', RDSNo)
		If EpiPro then
			WaferChars  = Xlate('RDS', RDSNo, 'WAFER_CHAR', 'X')
			PocketChars = Xlate('RDS', RDSNo, 'POCKET_CHAR', 'X')
			For each WaferChar in WaferChars using @VM setting vPos
				If WaferChar EQ 'PROD' then
					PocketChar = PocketChars<0, vPos>
					If PocketChar NE 'TEST' then TestRun = False$
				end
			Until TestRun EQ False$
			Next WaferChar
		end else
			Error_Services('Add', 'Error in ':Service:' service. This service only supports EpiPro runs.')
		end
	end else
		Error_Services('Add', 'Error in ':Service:' service. Null RDSNo passed in.')
	end
	Response = TestRun
	
end service


//----------------------------------------------------------------------------------------------------------------------
// IsValidRDS
//
// Params - RDSNo, RDSType
//			RDSNo can be acutal RDS or WMI for EPP (WO_NO.1.CASS_NO, e.g. 169515.1.20) or WO_MAT for GAN (WO_NO.CASS_NO, e.g. 169410.6)
// Returns True$ if RDS is valid
//----------------------------------------------------------------------------------------------------------------------
Service IsValidRDS(RDSNo, RDSType)
	IsValid = False$
	FirstChar = RDSNo[1,1]
	*debug
	Begin Case
		
		Case RDSType EQ 'SIC'
			If Num(RDSNo) then
				If XLate('RDS', RDSNo, 'SEQ', 'X') NE '' then
					IsValid = True$
				end
			end
			
		Case RDSType EQ 'EPP'
			CONVERT '.' TO '*' IN RDSNo
			If XLATE('WM_OUT',RDSNo,'SLOT_NO','X') NE '' then
				IsValid = True$
			end
			
		Case RDSTYpe EQ 'GAN'
			CONVERT '.' TO '*' IN RDSNo
			If XLATE('WO_MAT', RDSNo, 'RDS_NO', 'X') NE '' then
				IsValid = True$
			end
	End Case
	
	Response = IsValid
end service

Service IsValidSubLot(RDSNo, RDSType, SupplierLot)
	IsValid = False$
	rec = ''
	*debug
	Begin Case
		
		Case RDSType EQ 'SIC'
			compareLot = XLATE('RDS', RDSNo, RDS_LOT_NUM$, 'X')
			if compareLot EQ SupplierLot then
				IsValid = True$
			end
			
		Case RDSType EQ 'EPP'
			CONVERT '.' TO '*' IN RDSNo
			compareLot = XLATE('WM_IN', RDSNo, 'LOT_NO', 'X')
			if compareLot EQ SupplierLot then
				IsValid = True$
			end
			
		Case RDSType EQ 'GAN'
			CONVERT '.' TO '*' IN RDSNo
			compareLot = XLATE('WO_MAT', RDSNo, WO_MAT_LOT_NO$, 'X')
			if compareLot EQ SupplierLot then
				IsValid = True$
			end
			
	End Case
	
	Response = IsValid
end service

Service GetRDSRunHistoryByReactorAndWO(ReactorNo, WO)
	StartTick = GetTickCount()
    MetricName = 'GetRDSRunHistoryByReactorAndWO'
	
	table = "RDS"
	ErrorMessage = ''
	RDSRecords = ''
	RDSSortedByAssignmentDTM = ''
	Open "DICT ":table To @DICT Else
		ErrorMessage = 'Error opening RDS dictionary'
	End
	If ErrorMessage EQ '' then
		srch_strng  = "WO":@VM:WO:@FM:"REACTOR":@VM:ReactorNo:@FM
		option      = ""
		flag        = ""
		Btree.Extract(srch_strng, table, @DICT, RDSRecords, option, flag)
	end
    RDSRecordsWithPreEpiSig = ''
    for each RDSKey in RDSRecords using @VM setting RDSPos
    	ThisRDSRec = Database_Services('ReadDataRow', 'RDS', RDSKey, True$, 0, False$)
    	ThisRDSPreEpiDT = ThisRDSRec<RDS_PRE_EPI_SIG_DATE$>
    	ThisRDSPreEpiTM = ThisRDSRec<RDS_PRE_EPI_SIG_TIME$>
    	ThisRDSPreEpiDTM = SRP_Datetime('Combine', ThisRDSPreEpiDT, ThisRDSPreEpiTM)
    	RDSRecordsWithPreEpiSig<1, RDSPos> = RDSKey
    	RDSRecordsWithPreEpiSig<2, RDSPos> = ThisRDSPreEpiDTM
    Next RDSKey
    RDSSortedByAssignmentDTM = SRP_Sort_Array(RDSRecordsWithPreEpiSig, 'AN2', 0)
    FinalRDSSortedByAssignmentDTM = ''
    
    for each RDS in RDSSortedByAssignmentDTM<1> using @VM setting SortedRDSPos
    	ThisRDSDtm = RDSSortedByAssignmentDTM<2, SortedRDSPos>
    	if ThisRDSDtm GT 0 then
    		FinalRDSSortedByAssignmentDTM<1, -1> = RDS
    		FinalRDSSortedByAssignmentDTM<2, -1> = OConv(ThisRDSDtm, 'DT')
    	end
    Next RDS
    Response = FinalRDSSortedByAssignmentDTM<1>
    
    EndTick = GetTickCount()
    Mona_Services('QueueLatencyAndCountMetrics', MonaResource, MetricName, StartTick, EndTick)
end service


Service IsTWLoggingReqd(RDSNo)
	StartTick = GetTickCount()
    MetricName = 'IsTWLoggingReqd'
	
    IsTWReqd            = False$
    ExistingTWRuns      = Test_Run_Services('GetTestRunKeysByRDS', RDSNo)
    If ExistingTWRuns EQ '' then
        RDSRec              = Database_Services('ReadDataRow', 'RDS', RDSNo)
        WONo                = RDSRec<RDS_WO$>
        ReactNo             = RDSRec<RDS_REACTOR$>
        ReactorType         = Database_Services('ReadDataColumn', 'REACTOR', ReactNo, REACTOR_REACT_TYPE$, True$, 0, False$)
        ThisReactorRunOrder = 0
        ReactRDSNos         = RDS_Services('GetRDSRunHistoryByReactorAndWO', ReactNo, WONo)
        LOCATE RDSNo IN ReactRDSNos USING @VM SETTING ThisReactorRunOrder ELSE Return
        PSNo                = RDSRec<RDS_PROD_SPEC_ID$>
        PSRec               = Database_Services('ReadDataRow', 'PROD_SPEC', PSNo)
        PRSLayerKeys        = PSRec<PROD_SPEC_PRS_LAYER_KEY$>
        for each PRSLayerKey in PRSLayerKeys using @VM
            PRSPropKeys = Database_Services('ReadDataColumn', 'PRS_LAYER', PRSLayerKey, 5)
            for each PRSPropKey in PRSPropKeys using @VM
                PRSPropRec = Database_Services('ReadDataRow', 'PRS_PROP', PRSPropKey)
                WaferType = PRSPropRec<6>
                TestType = Field(PRSPropKey, '*', 3)
                Layer = Field(PRSPropKey, '*', 2)
                MeasureFreq = PRSPropRec<PRS_PROP_FREQ$>
                Start = PRSPropRec<PRS_PROP_MET_START$>
                If WaferType NE 'Product' AND WaferType NE 'Prod' AND WaferType NE '' then
                    BEGIN CASE
                        CASE MeasureFreq = 'F' AND ThisReactorRunOrder = 1	; IsTWReqd = 1
                        CASE ThisReactorRunOrder = Start					; IsTWReqd = 1
                        CASE NUM(MeasureFreq)
                            Begin Case
                                Case ReactorType EQ 'HTR'
                                    //Logging required on lot the data gets logged to
                                    IF MOD(ThisReactorRunOrder,MeasureFreq) - Start = 0 THEN
                                        IsTWReqd = True$
                                    END
                                Case ReactorType EQ 'ASM'
                                    //Logging required on lot the data gets logged to
                                    IF MOD(ThisReactorRunOrder,MeasureFreq) - Start = 0 THEN
                                        IsTWReqd = True$
                                    END
                                Case ReactorType EQ 'ASM+'
                                    //Logging required on lot prior to the one the data gets logged to.
                                    IF MOD(ThisReactorRunOrder,MeasureFreq) = 0 THEN
                                        IsTWReqd = True$
                                    END
                            End Case
                            
                    END CASE
                end
            Until IsTWReqd EQ True$
            Next PRSPropKey
        Until IsTWReqd EQ True$
        Next PRSLayerKey
    end else
        IsTWReqd = False$
    end
	//The code below will be removed upon final production usage.
    TWTrackingSystemActive = Database_Services('ReadDataColumn', 'APP_INFO', 'NEW_TW_SYSTEM_ACTIVE_SWITCH', 1, True$, 0, False$)
    If TWTrackingSystemActive EQ False$ then
        IsTWReqd = False$
    end
    Response = IsTWReqd
    
    EndTick = GetTickCount()
    Mona_Services('QueueLatencyAndCountMetrics', MonaResource, MetricName, StartTick, EndTick)
end service

Service TestWaferRanAfterLoad(RDSNo)
	TestWaferRanAfterLoad = False$
	TestWaferKeys = Test_Run_Services('GetTestRunKeysByRDS', RDSNo)
	for each Key in TestWaferKeys using @VM
		TestWaferDtm = Xlate('TEST_RUN', Key, TEST_RUN_RUN_DTM$, 'X')
		TestWaferDate = SRP_DateTime('Date', TestWaferDtm)
		TestWaferTime = SRP_DateTime('Time', TestWaferDtm)
		
		RdsDateIn = Xlate('RDS', RDSNo, RDS_DATE_IN$, 'X')
		RdsTimeIn = Xlate('RDS', RDSNo, RDS_TIME_IN$, 'X')
		
		TestWaferRanAfterLoad = TestWaferDate > RdsDateIn
		If TestWaferRanAfterLoad EQ False$ then
			TestWaferRanAfterLoad = (TestWaferDate EQ RdsDateIn) and TestWaferTime GT RdsTimeIn
		end
	Until TestWaferRanAfterLoad EQ True$
	Next Key
	
	Response = TestWaferRanAfterLoad
end service

//----------------------------------------------------------------------------------------------------------------------
// VerifyEPPMetrology
//
// RDSNo. - [Required]
//
// Sets an error in Error_Services if there is an issue with the metrology data.
//----------------------------------------------------------------------------------------------------------------------
Service VerifyEPPMetrology(RDSNo)
	
	If RDSNo NE '' then
		ErrorMsg                    = ''
		// ActivateRecord allows for the use of the curly brace notation {}
		Database_Services('ActivateRecord', 'RDS', RDSNo)
		
		If {REACTOR_TYPE} EQ 'EPP' then
			LayerList                   = ''    
			LayerList<COL$LSID>         = {RDS_LS_ID}
			LayerList<COL$RECIPE>       = {LS_RECIPE}
			LayerList<COL$RECIPE_NAME>  = {LS_RECIPE_NAME}
			LayerList<COL$PARAMS>       = {LS_PARMS_COMP}
			LayerList<COL$PARMS_OUT>    = {PARAM_OUT_OF_SPEC}
			LayerList<COL$BLANK_COL>    = ''
			LayerList<COL$MET_NO>       = {MET_KEYS}
			LayerList<COL$THICK_AVG>    = {TTHICK_AVG_ALL}
			LayerList<COL$RRHO_AVG>     = {TRES_AVG_ALL}
			LayerList<COL$MET_NO_Z1>    = {MET_KEYS_Z1}
			LayerList<COL$THICK_AVG_Z1> = {TTHICK_AVG_ALL_Z1}
			LayerList<COL$RRHO_AVG_Z1>  = {TRES_AVG_ALL_Z1}
			LayerList<COL$MET_NO_Z2>    = {MET_KEYS_Z2}
			LayerList<COL$THICK_AVG_Z2> = {TTHICK_AVG_ALL_Z2}
			LayerList<COL$RRHO_AVG_Z2>  = {TRES_AVG_ALL_Z2}
			LayerList<COL$MET_OUT>      = {MET_OUT_OF_SPEC}
			LayerList<COL$MET_OUT_Z1>   = {MET_OUT_OF_SPEC_Z1}
			LayerList<COL$MET_OUT_Z2>   = {MET_OUT_OF_SPEC_Z2}
			LayerList<COL$MET_TWSIG>    = {MET_TW_SIGNED}
			LayerList<COL$MET_TWSIG_Z1> = {MET_TW_SIGNED_Z1}
			LayerList<COL$MET_TWSIG_Z2> = {MET_TW_SIGNED_Z2}       
			LayerList                   = SRP_Array('Rotate', LayerList, @FM, @VM)
			
			// Improvement for EpiPro to not consider a metrology zone "Out of Spec" if all wafers from that zone
			// have been scrapped (i.e. the wafers were either test wafers or were NCR'd)
			
			! WM_OUT data is duplicated here in the RDS and cannot be trusted (i.e., it is not kept in sync properly).
			! We should consider modifying this to look at WM_OUT for NCR keys associated with the pocket.
			
			Pockets        = {POCKET}
			Zones          = {ZONE}
			PocketChars    = {POCKET_CHAR}
			OutNCRs        = {OUT_NCR}
			Zone1Scrapped  = False$
			Zone2Scrapped  = False$
			NumZone1Wfrs   = 0
			NumZone2Wfrs   = 0
			NumZone1Scraps = 0
			NumZone2Scraps = 0
			Pocket         = ''
			For each Pocket in Pockets using @VM setting vPos
				Zone       = Zones<0, vPos>
				PocketChar = PocketChars<0, vPos>
				OutNCR     = OutNCRs<0, vPos>
				Begin Case
					Case Zone EQ 1
						If PocketChar NE 'DUMMY' then NumZone1Wfrs += 1
					Case Zone EQ 2
						If PocketChar NE 'DUMMY' then NumZone2Wfrs += 1
				End Case
				Begin Case
					Case ( ( (PocketChar EQ 'TEST') or (OutNCR NE '') ) and (Zone EQ 1) )
						NumZone1Scraps += 1
					Case ( ( (PocketChar EQ 'TEST') or (OutNCR NE '') ) and (Zone EQ 2) )
						NumZone2Scraps += 1
				End Case
			Next Pocket
			
			If NumZone1Wfrs EQ NumZone1Scraps then Zone1Scrapped = True$
			If NumZone2Wfrs EQ NumZone2Scraps then Zone2Scrapped = True$
			/////////////////////////////////////////////////////////////////////////////////////////////////
			
			
			FOR I = 1 TO COUNT(LayerList,@FM) + (LayerList NE '')
				
				* Layer Set Parameters
				
				IF ICONV(LayerList<I,COL$PARMS_OUT>,'B') = 1 ELSE
					IF ICONV(LayerList<I,COL$PARAMS>,'B') = 0 THEN
						ErrorMsg = 'Recipe Parameters incomplete for RDS: ':RDSNo
					END
				END
				
				* ASM Metrology
				
				IF LayerList<I,COL$MET_NO> NE '' THEN
					MetNo 	= LayerList<I,COL$MET_NO>
					MetRec 	= XLATE('RDS_TEST',MetNo,'','X')
					
					TWSigned 	= obj_RDS_Test('TWSignedOff',MetNo:@RM:MetRec)
					TestComp 	= obj_RDS_Test('TestComplete',MetNo:@RM:MetRec)
					OutOfSpec	= obj_RDS_Test('OutOfSpec',MetNo:@RM:MetRec)
					
					IF NOT(obj_RDS_Test('TestComplete',MetNo:@RM:MetRec)) THEN
						ErrorMsg = 'Metrology not signed off for Met No: ':MetNo: 'for RDS: ':RDSNo
					END ELSE
						
						IF (obj_RDS_Test('OutOfSpec',MetNo:@RM:MetRec) = 1) THEN
							ErrorMsg = 'Metrology ':MetNo:' is out of spec for RDS: ':RDSNo
							
						END ELSE
							IF LayerList<I,COL$THICK_AVG> = '' AND LayerList<I,COL$RRHO_AVG> = '' AND LayerList<I,COL$MET_NO> NE '' THEN
								ErrorMsg = 'Metrology incomplete for RDS: ':RDSNo
							END
						END
					END
				end
				
				* Zone 1 EpiPRO Metrology
				// Update logic to not throw an error if wafers from a failed zone are NCR'd
				IF LayerList<I,COL$MET_NO_Z1> NE '' THEN
					MetNo 	= LayerList<I,COL$MET_NO_Z1>
					MetRec 	= XLATE('RDS_TEST',MetNo,'','X')
					
					TWSigned 	= obj_RDS_Test('TWSignedOff',MetNo:@RM:MetRec)
					TestComp 	= obj_RDS_Test('TestComplete',MetNo:@RM:MetRec)
					OutOfSpec	= obj_RDS_Test('OutOfSpec',MetNo:@RM:MetRec)
					
					IF NOT(obj_RDS_Test('TestComplete',MetNo:@RM:MetRec)) THEN
						ErrorMsg = 'Zone 1 Metrology is Incomplete for RDS: ':RDSNo
					END ELSE
						
						IF ( (obj_RDS_Test('OutOfSpec',MetNo:@RM:MetRec) = 1) and (Zone1Scrapped EQ False$) ) THEN
							ErrorMsg = 'Zone 1 Metrology is Out of Spec for RDS: ':RDSNo
						END ELSE
							IF LayerList<I,COL$THICK_AVG_Z1> = '' AND LayerList<I,COL$RRHO_AVG_Z1> = '' AND LayerList<I,COL$MET_NO_Z1> NE '' THEN
								ErrorMsg = 'Zone 1 Metrology is Incomplete for RDS: ':RDSNo
							END
							//Added check for missing res avg data - JRO
							/*IF LayerList<I,COL$THICK_AVG_Z1> NE '' AND LayerList<I,COL$RRHO_AVG_Z1> = '' AND LayerList<I,COL$MET_NO_Z1> NE '' THEN
							                                If Error_Services('NoError') then Error_Services('Add', 'Zone 1 Metrology is missing Resistivity data for RDS: ':RDSNo)
							                            END
							                            */
						END
					END
				END
				
				* Zone 2 EpiPRO Metrology
				// Update logic to not throw an error if wafers from a failed zone are NCR'd
				IF LayerList<I,COL$MET_NO_Z2> NE '' THEN
					MetNo 	= LayerList<I,COL$MET_NO_Z2>
					MetRec 	= XLATE('RDS_TEST',MetNo,'','X')
					
					TWSigned 	= obj_RDS_Test('TWSignedOff',MetNo:@RM:MetRec)
					TestComp 	= obj_RDS_Test('TestComplete',MetNo:@RM:MetRec)
					OutOfSpec	= obj_RDS_Test('OutOfSpec',MetNo:@RM:MetRec)
					
					IF NOT(obj_RDS_Test('TestComplete',MetNo:@RM:MetRec)) THEN
						ErrorMsg = 'Zone 2 Metrology is Incomplete for RDS: ':RDSNo
					END ELSE
						
						IF ( (obj_RDS_Test('OutOfSpec',MetNo:@RM:MetRec) = 1) and (Zone2Scrapped EQ False$) ) THEN
							ErrorMsg = 'Zone 2 Metrology is Out of Spec for RDS: ':RDSNo
						END ELSE
							IF LayerList<I,COL$THICK_AVG_Z2> = '' AND LayerList<I,COL$RRHO_AVG_Z2> = '' AND LayerList<I,COL$MET_NO_Z2> NE '' THEN
								ErrorMsg = 'Zone 2 Metrology is Incomplete for RDS: ':RDSNo
							END
							/*
							                            //Added check for missing res avg data - JRO
							                            IF LayerList<I,COL$THICK_AVG_Z2> NE '' AND LayerList<I,COL$RRHO_AVG_Z2> = '' AND LayerList<I,COL$MET_NO_Z2> NE '' THEN
							                                If Error_Services('NoError') then Error_Services('Add', 'Zone 2 Metrology is missing Resistivity data for RDS: ':RDSNo)
							                            END
							                            */
						END
					END
				END		
				
			NEXT I
		end else
			ErrorMsg = 'Non-EpiPro RDS passed to RDS_Services(':Service:')'
		end
	end else
		ErrorMsg = 'Null RDS value passed to RDS_Services(':Service:')'
	end
	If ErrorMsg NE '' then Error_Services('Add', ErrorMsg)
	
end service


Service RDSVerifyInst(RDSNo, Stage=STAGES)
	
	Database_Services('ActivateRecord', 'RDS', RDSNo)
	VerInst = ''
	Begin Case
		Case Stage EQ 'PRE'
			VerInst = {PRE_INST}
		Case Stage EQ 'FWI'
			VerInst = {FWI_INST}
		Case Stage EQ 'LWI'
			VerInst = {LWI_INST}
		Case Stage EQ 'QA'
			VerInst = {QA_INST}
		Case Stage EQ 'LOAD'
			VerInst = {LOAD_INST}
		Case Stage EQ 'UNLOAD'
			VerInst = {UNLOAD_INST}
		Case Stage EQ 'POST'
			VerInst = {POST_INST}
	End Case
	Response = Dialog_Box('RDS_VER', @Window, VerInst)
	Begin Case
		Case Stage EQ 'PRE'
			{PRE_INST_ACK}      = Response
		Case Stage EQ 'FWI'
			{FWI_INST_ACK}      = Response
		Case Stage EQ 'LWI'
			{LWI_INST_ACK}      = Response
		Case Stage EQ 'QA'
			{QA_INST_ACK}       = Response
		Case Stage EQ 'LOAD'
			{LOAD_INST_ACK}     = Response
		Case Stage EQ 'UNLOAD'
			{UNLOAD_INST_ACK}   = Response
		Case Stage EQ 'POST'
			{POST_INST_ACK}     = Response
	End Case
	
	Database_Services('WriteDataRow', 'RDS', RDSNo, @Record, True$, False$, True$)
	
end service


Service CopyRDSLayerParameters(RDSNo)
	StartTick = GetTickCount()
    MetricName = 'CopyRDSLayerParameters'
	
	If (RDSNo NE '') then
		RunOrderNo  = Xlate('RDS', RDSNo, 'RUN_ORDER_NUM', 'X')
		If (RunOrderNo GT 1) then
			PrevRunNo = RunOrderNo - 1
			WONo         = Xlate('RDS', RDSNo, 'WO', 'X')
			CopyWOMatKey = WONo:'*':PrevRunNo
			CopyRDSNo    = Xlate('WO_MAT', CopyWOMatKey, 'RDS_NO', 'X') 
			// Get RDSLayer keys -> copy tool parameters to each layer
			CopyRDSLayerKeys = Xlate('RDS', CopyRDSNo, 'RDS_LAYER_KEYS', 'X')
			If CopyRDSLayerKeys NE '' then
				For each CopyRDSLayerKey in CopyRDSLayerKeys
					// Copy RDS layer records
					LayerID = Field(CopyRDSLayerKey, '*', 2)
					DestRDSLayerKey = RDSNo:'*':LayerID
					CopyRDSLayerRec = Database_Services('ReadDataRow', 'RDS_LAYER', CopyRDSLayerKey)                       
					DestRDSLayerRec = Database_Services('ReadDataRow', 'RDS_LAYER', DestRDSLayerKey)
					
					DestRDSLayerRec<RDS_LAYER_DOPANT$>            = CopyRDSLayerRec<RDS_LAYER_DOPANT$>
					DestRDSLayerRec<RDS_LAYER_EPI_DILUENT$>       = CopyRDSLayerRec<RDS_LAYER_EPI_DILUENT$>  
					DestRDSLayerRec<RDS_LAYER_EPI_TIME$>          = CopyRDSLayerRec<RDS_LAYER_EPI_TIME$>
					DestRDSLayerRec<RDS_LAYER_DILUENT_ADJ_PARAM$> = CopyRDSLayerRec<RDS_LAYER_DILUENT_ADJ_PARAM$>
					DestRDSLayerRec<RDS_LAYER_DOPANT_FLOW$>       = CopyRDSLayerRec<RDS_LAYER_DOPANT_FLOW$>
					DestRDSLayerRec<RDS_LAYER_HCL_FLOW$>          = CopyRDSLayerRec<RDS_LAYER_HCL_FLOW$> 
					DestRDSLayerRec<RDS_LAYER_BAKE_TIME$>         = CopyRDSLayerRec<RDS_LAYER_BAKE_TIME$>
					DestRDSLayerRec<RDS_LAYER_EPI_H2_FLOW$>       = CopyRDSLayerRec<RDS_LAYER_EPI_H2_FLOW$>
					DestRDSLayerRec<RDS_LAYER_TCS_FLOW$>          = CopyRDSLayerRec<RDS_LAYER_TCS_FLOW$>
					DestRDSLayerRec<RDS_LAYER_DCS_FLOW$>          = CopyRDSLayerRec<RDS_LAYER_DCS_FLOW$>
					DestRDSLayerRec<RDS_LAYER_AUX1$>              = CopyRDSLayerRec<RDS_LAYER_AUX1$>
					DestRDSLayerRec<RDS_LAYER_AUX2$>              = CopyRDSLayerRec<RDS_LAYER_AUX2$>
					DestRDSLayerRec<RDS_LAYER_F_OFFSET$>          = CopyRDSLayerRec<RDS_LAYER_F_OFFSET$>
					DestRDSLayerRec<RDS_LAYER_S_OFFSET$>          = CopyRDSLayerRec<RDS_LAYER_S_OFFSET$>
					DestRDSLayerRec<RDS_LAYER_R_OFFSET$>          = CopyRDSLayerRec<RDS_LAYER_R_OFFSET$>
					DestRDSLayerRec<RDS_LAYER_ETCH1$>             = CopyRDSLayerRec<RDS_LAYER_ETCH1$> 
					DestRDSLayerRec<RDS_LAYER_ETCH2$>             = CopyRDSLayerRec<RDS_LAYER_ETCH2$> 
					DestRDSLayerRec<RDS_LAYER_ETCH3$>             = CopyRDSLayerRec<RDS_LAYER_ETCH3$> 
					DestRDSLayerRec<RDS_LAYER_OVERGROW_REQ$>      = CopyRDSLayerRec<RDS_LAYER_OVERGROW_REQ$> 
					DestRDSLayerRec<RDS_LAYER_SUSC_ETCH$>         = CopyRDSLayerRec<RDS_LAYER_SUSC_ETCH$>
					DestRDSLayerRec<RDS_LAYER_UL_TEMP$>           = CopyRDSLayerRec<RDS_LAYER_UL_TEMP$>                                  
					DestRDSLayerRec<RDS_LAYER_MODIFY_USER$, -1>   = 'CopyService'
					DestRDSLayerRec<RDS_LAYER_MODIFY_DTM$, -1>    = DateTime()
					Database_Services('WriteDataRow', 'RDS_LAYER', DestRDSLayerKey, DestRDSLayerRec, True$, True$, True$)
					
				Next CopyRDSLayerKey
			end
		end else
			Error_Services('Add', 'No previous run to copy RDS layer parameters from in ':Service:' service.')
		end
	end else
		Error_Services('Add', 'RDSNo not supplied in ':Service:' service.')
	end
	
	EndTick = GetTickCount()
    Mona_Services('QueueLatencyAndCountMetrics', MonaResource, MetricName, StartTick, EndTick)
end service


Service RemovePocketNCR(RDSNo, Pocket)
	
	If ( (RDSNo NE '') and (Pocket NE '') ) then
		RDSRec = Database_Services('ReadDataRow', 'RDS', RDSNo)
		If Error_Services('NoError') then
			RDSRec<RDS_OUT_NCR$, Pocket> = ''
			Database_Services('WriteDataRow', 'RDS', RDSNo, RDSRec)
		end
	end
	
end service


Service AddPocketNCR(RDSNo, Pocket, NCRNo)
	
	If ( (RDSNo NE '') and (Pocket NE '') and (NCRNo NE '') ) then
		RDSRec = Database_Services('ReadDataRow', 'RDS', RDSNo)
		If Error_Services('NoError') then
			RDSRec<RDS_OUT_NCR$, Pocket> = NCRNo
			Database_Services('WriteDataRow', 'RDS', RDSNo, RDSRec)
		end
	end
	
end service


Service ConvertRecordToJSON(KeyID, Record, itemURL)
	jsonRecord    = ''
	If KeyID NE '' then
		
		If Record EQ '' then Record = Database_Services('ReadDataRow', 'RDS', KeyID)
		If Error_Services('NoError') then
			@DICT    = Database_Services('GetTableHandle', 'DICT.RDS')
			@ID      = KeyID
			@RECORD  = Record
			If SRP_JSON(objJSON, 'New', 'Object') then
				
				If SRP_JSON(objRDS, 'New', 'Object') then
					WOMatKey     = {WO_MAT_KEY}
					SAPBatchNo   = Database_Services('ReadDataColumn', 'WO_MAT', WOMatKey, WO_MAT_SAP_BATCH_NO$, True$, 0, False$)
					SRP_JSON(objRDS, 'SetValue', 'keyId', @ID)
					SRP_JSON(objRDS, 'SetValue', 'reactor', {REACTOR})
					SRP_JSON(objRDS, 'SetValue', 'workOrder', {WO})
					SRP_JSON(objRDS, 'SetValue', 'cassNo', {CASS_NO})
					SRP_JSON(objRDS, 'SetValue', 'combStatus', {COMB_STATUS})
					SRP_JSON(objRDS, 'SetValue', 'partNo', {PART_NUM})
					SRP_JSON(objRDS, 'SetValue', 'PSN', {PROD_SPEC_ID})
					SRP_JSON(objRDS, 'SetValue', 'cassNo', {CASS_NO})
					SRP_JSON(objRDS, 'SetValue', 'entryId', {ENTRY_ID})
					SRP_JSON(objRDS, 'SetValue', 'entryDtm', Trim(OConv({ENTRY_DATE}, 'D4/H'):' ':OConv({ENTRY_TIME}, 'MTHS')))
					SRP_JSON(objRDS, 'SetValue', 'preEpiSig', {PRE_EPI_SIG})
					SRP_JSON(objRDS, 'SetValue', 'preEpiSigDtm', Trim(OConv({PRE_EPI_SIG_DATE}, 'D4/H'):' ':OConv({PRE_EPI_SIG_TIME}, 'MTHS')))
					SRP_JSON(objRDS, 'SetValue', 'operatorIn', {OPERATOR_IN})
					SRP_JSON(objRDS, 'SetValue', 'dtmIn', Trim(OConv({DATE_IN}, 'D4/H'):' ':OConv({TIME_IN}, 'MTHS')))
					SRP_JSON(objRDS, 'SetValue', 'operatorOut', {OPERATOR_OUT})
					SRP_JSON(objRDS, 'SetValue', 'dtmOut', Trim(OConv({DATE_OUT}, 'D4/H'):' ':OConv({TIME_OUT}, 'MTHS')))
					SRP_JSON(objRDS, 'SetValue', 'postEpiSig', {POST_EPI_SIG})
					SRP_JSON(objRDS, 'SetValue', 'postEpiSigDtm', Trim(OConv({POST_EPI_SIG_DATE}, 'D4/H'):' ':OConv({POST_EPI_SIG_TIME}, 'MTHS')))
					SRP_JSON(objRDS, 'SetValue', 'SapBatchNo', SAPBatchNo)
					SAPTxDtm     = Xlate('WO_MAT', {WO_MAT_KEY}, 'SAP_TX_DTM', 'X')
					SRP_JSON(objRDS, 'SetValue', 'sapTxDtm', Trim(OConv(SAPTxDtm, 'DT2/^H')))
					SRP_JSON(objRDS, 'SetValue', 'supVerSig', {SUP_VER_SIG})
					SRP_JSON(objRDS, 'SetValue', 'supVerSigDtm', Trim(OConv({SUP_VER_SIG_DATE}, 'D4/H'):' ':OConv({SUP_VER_SIG_TIME}, 'MTHS')))
					SRP_JSON(objRDS, 'SetValue', 'shipDtm', Trim(OConv({SHIP_DATE}, 'D4/H'):' ':OConv({SHIP_TIME}, 'MTHS')))
					SRP_JSON(objRDS, 'SetValue', 'subPartNo', {SUB_PART_NO})
					SRP_JSON(objRDS, 'SetValue', 'shipNo', {SHIP_NO})
					SRP_JSON(objRDS, 'SetValue', 'cassWaferQty', {CASS_WAFER_QTY})
					SRP_JSON(objRDS, 'SetValue', 'loadLockSide', {LOAD_LOCK_SIDE})
					SRP_JSON(objRDS, 'SetValue', 'waferSize', {WAFER_SIZE})
					SRP_JSON(objRDS, 'SetValue', 'reactorType', {REACTOR_TYPE})
					CurrWfrCnt   = Xlate('WO_MAT', {WO_MAT_KEY}, 'CURR_WFR_CNT', 'X')
					SRP_JSON(objRDS, 'SetValue', 'CURR_WFR_CNT', CurrWfrCnt)
					CustNo       = Database_Services('ReadDataColumn', 'WO_LOG', {WO}, WO_LOG_CUST_NO$, True$, 0, False$)
					CustReshipNo = Database_Services('ReadDataColumn', 'WO_MAT', {WO_MAT_KEY}, WO_MAT_RESHIP_CUST_NO$, True$, 0, False$)
					If CustReshipNo NE '' then
					    CustNo = CustReshipNo
					end
                    CustName      = Database_Services('ReadDataColumn', 'COMPANY', CustNo, COMPANY_CO_NAME$, True$, 0, False$)
                    CustAbbrev    = Database_Services('ReadDataColumn', 'COMPANY', CustNo, COMPANY_ABBREV$, True$, 0, False$)
                    SRP_JSON(objRDS, 'SetValue', 'CustNo', CustNo)
                    SRP_JSON(objRDS, 'SetValue', 'CustName', CustName)
                    SRP_JSON(objRDS, 'SetValue', 'CustAbbrev', CustAbbrev)

					// Save system variables
					AtID         = @ID
					AtRecord     = @Record
					AtDict       = @Dict
					
					// Add a PSN object
					ProdSpecJSON = PSN_Services('ConvertRecordToJSON', {PROD_SPEC_ID})
					If SRP_JSON(objProdSpecResponse, 'Parse', ProdSpecJSON) EQ '' then
						objProdSpec = SRP_JSON(objProdSpecResponse, 'Get', 'prodSpec')
						SRP_JSON(objRDS, 'Set', 'prodSpec', objProdSpec)
						SRP_JSON(objProdSpec, 'Release')
						SRP_JSON(objProdSpecResponse, 'Release')
					end
					
					// Restore system variables
					@ID     = AtID
					@Record = AtRecord
					@Dict   = AtDict
					
					// Add CLEAN_INSP objects
					CINos        = ''
					CINos<0, -1> = {PRE_CI_NO}
					CINos<0, -1> = {FWI_CI_NO}
					CINos<0, -1> = {LWI_CI_NO}
					CINos<0, -1> = {POST_CI_NO}
					
					If CINos NE '' then
						If SRP_JSON(objCleanInspArray, 'New', 'Array') then
							For each CINo in CINos using @VM setting vPos
								If CINo NE '' then
									CleanInspJSON = Clean_Insp_Services('ConvertRecordToJSON', CINo)
									If SRP_JSON(objCleanInsp, 'Parse', CleanInspJSON) EQ '' then
										objTemp = SRP_JSON(objCleanInsp, 'Get', 'cleanInsp')
										SRP_JSON(objCleanInspArray, 'Add', objTemp)
										SRP_JSON(objTemp, 'Release')
										SRP_JSON(objCleanInsp, 'Release')
									end
								end
							Next CINo
							SRP_JSON(objRDS, 'Set', 'cleanInsp', objCleanInspArray)
							SRP_JSON(objCleanInspArray, 'Release')
						end
					end
					// Save system variables
					// Restore system variables
					@ID     = AtID
					@Record = AtRecord
					@Dict   = AtDict
					AtID         = @ID
					AtRecord     = @Record
					AtDict       = @Dict
					
					If RowExists('WO_MAT_QA', WOMatKey) then
						// Add WO_MAT_QA object
						WOMatQAJSON = WO_Mat_QA_Services('ConvertRecordToJSON', WOMatKey)
						If SRP_JSON(objWOMatQA, 'Parse', WOMatQAJSON) EQ '' then
							objTemp = SRP_JSON(objWOMatQA, 'Get', 'woMatQa')
							SRP_JSON(objRDS, 'Set', 'woMatQA', objTemp)
							SRP_JSON(objTemp, 'Release')
							SRP_JSON(objWOMatQA, 'Release')
						end
					end else
						If SRP_JSON(objWOMatQA, 'New', 'Object') then
							SRP_JSON(objRDS, 'Set', 'woMatQA', objWOMatQA)
							SRP_JSON(objWOMatQA, 'Release')
						end
						
					end
					// Restore system variables
					
					@ID     = AtID
					@Record = AtRecord
					@Dict   = AtDict
					
					If SRP_JSON(objRDSLayerArray, 'New', 'Array') then
						RDSLayerKeys = {RDS_LAYER_KEYS}
						For each RDSLayerKey in RDSLayerKeys using @VM setting vPos
							//If SRP_JSON(objRDSLayer, 'New', 'Object') then
							//SRP_JSON(objRDSLayer, 'SetValue', 'keyId', RDSLayerKey)
							//SRP_JSON(objRDSLayerArray, 'Add', objRDSLayer)
							rdsLayerJson = Rds_Layer_Services('ConvertRecordToJSON', RDSLayerKey)
							If SRP_Json(objTempHandle, 'Parse', rdsLayerJson) EQ '' then
								SRP_JSON(objRDSLayerArray, 'Add', objTempHandle)
								SRP_JSON(objTempHandle, 'Release')
							end
							
							//SRP_JSON(objRDSLayer, 'Release')
							//end
						Next RDSLayerKey
						//SRP_JSON(objRDS, 'Set', 'rdsLayerIds', objRDSLayerArray)
						SRP_JSON(objRDS, 'Set', 'rdsLayers', objRDSLayerArray)
						SRP_JSON(objRDSLayerArray, 'Release')
					end
					AllRTFRecords = Return_To_Fab_Services('GetReturnToFabRecordIdByCassId', KeyID)
                    If AllRTFRecords NE '' then
                        objRTFRecords = ''
                        If SRP_JSON(objRTFRecords, 'New', 'Array') then
                            For each RTFRecordId in AllRTFRecords using @VM setting vPos
                                objRTF = ''
                                If SRP_JSON(objRTF, 'New', 'Object') then
                                   RTFRecord = Database_Services('ReadDataRow', 'RETURN_TO_FAB_LOTS', RTFRecordId, True$, 0, False$)
                                   SRP_JSON(objRTF, 'SetValue', 'ReturnToFabLotsId', RTFRecordId)
                                   SRP_JSON(objRTF, 'SetValue', 'StartDtm', OConv(RTFRecord<RETURN_TO_FAB_LOTS_MH_INIT_DTM$>, 'DT'))
                                   SRP_JSON(objRTF, 'SetValue', 'Completed', RTFRecord<RETURN_TO_FAB_LOTS_COMPLETED$>, 'Boolean')
                                   SRP_JSON(objRTFRecords, 'Set', 'ReturnToFabRecord', objRTF)
                                   SRP_JSON(objRTFRecords, 'Add', objRTF)
					               SRP_JSON(objRTF, 'Release')
                                end
                            Next RTFRecordId
                                
                            SRP_JSON(objRDS, 'Set', 'ReturnToFabRecords', objRTFRecords)
                            SRP_JSON(objRTFRecords, 'Release')
                        end
                    end else
                        SRP_JSON(objRDS, 'SetValue', 'ReturnToFabRecords', '')
                    end
					SRP_JSON(objJSON, 'Set', 'rds', objRDS)
					SRP_JSON(objRDS, 'Release')
				end
				
				If itemURL NE '' then
					// The itemURL was passed in so add HAL+JSON properties.
					
					// Create the _links property and then all link objects needed for this resource.
					If SRP_JSON(objLinks, 'New', 'Object') then
						// Create a self link.
						If SRP_JSON(objLink, 'New', 'Object') then
							SRP_JSON(objLink, 'SetValue', 'href', ItemURL, 'String')
							SRP_JSON(objLink, 'SetValue', 'title', 'Self', 'String')
							SRP_JSON(objLinks, 'Set', 'self', objLink)
							SRP_JSON(objLink, 'Release')
						end
						SRP_JSON(objJSON, 'Set', '_links', objLinks)
						SRP_JSON(objLinks, 'Release')
					end
					
					// Create the _class property for this resource.
					SRP_JSON(objJSON, 'SetValue', '_class', 'resource')
				end
				jsonRecord = SRP_JSON(objJSON, 'Stringify', 'Styled')
				SRP_JSON(objJSON, 'Release')
			end else
				Error_Services('Add', 'Unable to create JSON representation in the ' : Service : ' service.')
			end
		end
	end else
		Error_Services('Add', 'KeyID argument was missing in the ' : Service : ' service.')
	end
	
	Response    = jsonRecord
	
end service


Service GetRDSMoves(ReportDTM)
	
	DatesToSearch = ''
	ReportDTM     = FIELD(ReportDTM, ' ', 1)
	query = 'SELECT RDS WITH DATE_OUT EQ ': QUOTE(ReportDTM)
	
	Set_Status(0)
	RList(query, TARGET_ACTIVELIST$, "", "", "")
	IF Get_Status(errCode) THEN
		ErrMsg(errCode)
		RETURN
	END
	RDSList = ''
	IF @RecCount then
		EoF = 0
		NumKeys = @RecCount
		Cnt = 0
		
		Loop
			ReadNext RDSKey Else EoF = 1
		until EoF
			RDSList<-1> = RDSKey
			
		Repeat
		
	end
	GoSub ClearCursors
	Response = RDSList
	
end service


Service SignUnloadExtra1(RDSNo, LSLUserName)
	
	IF LSLUserName NE ''  AND RowExists('LSL_USERS', LSLUserName) then
		If RowExists('RDS', RDSNo) then
			RDSRec            = Database_Services('ReadDataRow', 'RDS', RDSNo)
			UnloadExtra1Ready = Qa_Services('UnloadExtra1stReady', RDSNo)
			If UnloadExtra1Ready then
				RDSRec<RDS_OP_OUT_EX1$>      = LSLUsername
				RDSRec<RDS_OP_OUT_EX1_DATE$> = SRP_Date('Today')
				RDSRec<RDS_OP_OUT_EX1_TIME$> = SRP_Time('Now')
				Database_Services('WriteDataRow', 'RDS', RDSNo, RDSRec, True$, False$, True$)
				Reactor                      = RDSRec<RDS_REACTOR$>
				If Error_Services('NoError') then
					Rds_Services('AddComment', RDSNo, 'Unload Extra 1 Signed from Reactor ' : Reactor, LSLUserName)
				end
				Reactor_Services('RemoveRDSFromReactorLoad', RDSNo, Reactor, LSLUserName)
			end else
				Error_Services('Add', 'Unload Extra 1 is not able to be signed at this stage')
			end
		end else
			Error_Services('Add', 'RDS ' : RDSNo : ' does not exist.')
		end
	end else
		Error_Services('Add', 'Invalid user passed to Sign Unload Extra 1 routine')
	end
	
end service


Service UnsignUnloadExtra1(RDSNo, LSLUserName)
	
	IF LSLUserName NE ''  AND RowExists('LSL_USERS', LSLUserName) then
		If RowExists('RDS', RDSNo) then
			RDSRec                  = Database_Services('ReadDataRow', 'RDS', RDSNo)
			Reactor                     = RDSRec<RDS_REACTOR$>
			If Reactor NE '' then
				CurrReactorLoadCapacity = Reactor_Services('GetReactorAvailChamberCount', Reactor)
				If CurrReactorLoadCapacity GT 0 then
					UnsignUnloadExtra1Ready = Qa_Services('UnsignUnloadExtra1stReady', RDSNo)
					if UnsignUnloadExtra1Ready then
						RDSRec<RDS_OP_OUT_EX1$>      = ''
						RDSRec<RDS_OP_OUT_EX1_DATE$> = ''
						RDSRec<RDS_OP_OUT_EX1_TIME$> = ''
						Database_Services('WriteDataRow', 'RDS', RDSNo, RDSRec, True$, False$, True$)
						Reactor                      = RDSRec<RDS_REACTOR$>
						If Error_Services('NoError') then
							Rds_Services('AddComment', RDSNo, 'Unload Extra 1 Unsigned on Reactor ' : Reactor, LSLUserName)
						end
						Reactor_Services('AddRDSToReactorLoad', RDSNo, Reactor)
					end else
						Error_Services('Add', 'Unsign Load Extra 1 is not able to be unsigned.')
					end
				end else
					Error_Services('Add', 'Reactor #' : Reactor : ' does not currently have the capacity to be loaded. Unload a lot to allow.')
				end
			end else
				Error_Services('Add', 'RDS is not assigned to a reactor.')
			end

		end else
			Error_Services('Add', 'RDS ' : RDSNo : ' does not exist.')
		end
	end else
		Error_Services('Add', 'Invalid user passed to Unsign Load Extra 1 routine')
	end
	
end service


Service SignLoadExtra1(RDSNo,LSLUserName)
	
	IF LSLUserName NE ''  AND RowExists('LSL_USERS', LSLUserName) then
		If RowExists('RDS', RDSNo) then
			RDSRec          = Database_Services('ReadDataRow', 'RDS', RDSNo)
			Reactor                     = RDSRec<RDS_REACTOR$>
			If Reactor NE '' then
				CurrReactorLoadCapacity = Reactor_Services('GetReactorAvailChamberCount', Reactor)
				If CurrReactorLoadCapacity GT 0 then
					LoadExtra1Ready = Qa_Services('LoadExtra1stReady', RDSNo)
					If LoadExtra1Ready then
						RDSRec<RDS_OP_IN_EX2$>      = LSLUsername
						RDSRec<RDS_OP_IN_EX2_DATE$> = SRP_Date('Today')
						RDSRec<RDS_OP_IN_EX2_TIME$> = SRP_Time('Now')
						Database_Services('WriteDataRow', 'RDS', RDSNo, RDSRec, True$, False$, True$)
						If Error_Services('NoError') then
							Rds_Services('AddComment', RDSNo, 'Load Extra 1 Signed to Reactor ' : Reactor, LSLUserName)
						end
						Reactor_Services('AddRDSToReactorLoad', RDSNo, Reactor)
					end else
						Error_Services('Add', 'Load Extra 1 is not able to be signed at this stage')
					end
				end else
					Error_Services('Add', 'Reactor #' : Reactor : ' does not currently have the capacity to be loaded. Unload a lot to allow.')
				end
			end else
				Error_Services('Add', 'RDS is not assigned to a reactor.')
			end
			
		end else
			Error_Services('Add', 'RDS ' : RDSNo : ' does not exist.')
		end
	end else
		Error_Services('Add', 'Invalid user passed to Sign Unload Extra 1 routine')
	end
	
end service


Service UnsignLoadExtra1(RDSNo, LSLUserName)
	
	IF LSLUserName NE ''  AND RowExists('LSL_USERS', LSLUserName) then
		If RowExists('RDS', RDSNo) then
			RDSRec                = Database_Services('ReadDataRow', 'RDS', RDSNo)
			UnsignLoadExtra1Ready = Qa_Services('UnsignLoadExtra1stReady', RDSNo)
			if UnsignLoadExtra1Ready then
				RDSRec<RDS_OP_IN_EX2$>      = ''
				RDSRec<RDS_OP_IN_EX2_DATE$> = ''
				RDSRec<RDS_OP_IN_EX2_TIME$> = ''
				Database_Services('WriteDataRow', 'RDS', RDSNo, RDSRec, True$, False$, True$)
				Reactor                     = RDSRec<RDS_REACTOR$>
				If Error_Services('NoError') then
					Rds_Services('AddComment', RDSNo, 'Load Extra 1 Unsigned on Reactor ' : Reactor, LSLUserName)
				end
				Reactor_Services('RemoveRDSFromReactorLoad', RDSNo, Reactor, LSLUserName)
			end else
				Error_Services('Add', 'Unsign Load Extra 1 is not able to be unsigned.')
			end
		end else
			Error_Services('Add', 'RDS ' : RDSNo : ' does not exist.')
		end
	end else
		Error_Services('Add', 'Invalid user passed to Unsign Load Extra 1 routine')
	end
	
end service


Service SignUnloadExtra2(RDSNo,LSLUserName)
	
	IF LSLUserName NE ''  AND RowExists('LSL_USERS', LSLUserName) then
		If RowExists('RDS', RDSNo) then
			RDSRec              = Database_Services('ReadDataRow', 'RDS', RDSNo)
			UnloadExtra2ndReady = Qa_Services('UnloadExtra2ndReady', RDSNo)
			If UnloadExtra2ndReady then
				RDSRec<RDS_OP_OUT_EX2$>      = LSLUsername
				RDSRec<RDS_OP_OUT_EX2_DATE$> = SRP_Date('Today')
				RDSRec<RDS_OP_OUT_EX2_TIME$> = SRP_Time('Now')
				Database_Services('WriteDataRow', 'RDS', RDSNo, RDSRec, True$, False$, True$)
				Reactor                      = RDSRec<RDS_REACTOR$>
				If Error_Services('NoError') then
					Rds_Services('AddComment', RDSNo, 'Unload Extra 2 Signed from Reactor ' : Reactor, LSLUserName)
				end
				Reactor_Services('RemoveRDSFromReactorLoad', RDSNo, Reactor, LSLUserName)
			end else
				Error_Services('Add', 'Unload Extra 2 is not able to be signed at this stage')
			end
		end else
			Error_Services('Add', 'RDS ' : RDSNo : ' does not exist.')
		end
	end else
		Error_Services('Add', 'Invalid user passed to Sign Unload Extra 1 routine')
	end
	
end service


Service UnsignUnloadExtra2(RDSNo, LSLUserName)
	
	IF LSLUserName NE ''  AND RowExists('LSL_USERS', LSLUserName) then
		If RowExists('RDS', RDSNo) then
			RDSRec                  = Database_Services('ReadDataRow', 'RDS', RDSNo)
			Reactor                     = RDSRec<RDS_REACTOR$>
			If Reactor NE '' then
				CurrReactorLoadCapacity = Reactor_Services('GetReactorAvailChamberCount', Reactor)
				If CurrReactorLoadCapacity GT 0 then
					UnsignUnloadExtra2Ready = Qa_Services('UnsignUnloadExtra2ndReady', RDSNo)
					if UnsignUnloadExtra2Ready then
						RDSRec<RDS_OP_OUT_EX2$>      = ''
						RDSRec<RDS_OP_OUT_EX2_DATE$> = ''
						RDSRec<RDS_OP_OUT_EX2_TIME$> = ''
						Database_Services('WriteDataRow', 'RDS', RDSNo, RDSRec, True$, False$, True$)
						Reactor = RDSRec<RDS_REACTOR$>
						If Error_Services('NoError') then
							Rds_Services('AddComment', RDSNo, 'Unload Extra 2 Unsigned on Reactor ' : Reactor, LSLUserName)
						end
						Reactor_Services('AddRDSToReactorLoad', RDSNo, Reactor)
					end else
						Error_Services('Add', 'Unsign Load Extra 2 is not able to be unsigned.')
					end
				end else
					Error_Services('Add', 'Reactor #' : Reactor : ' does not currently have the capacity to be loaded. Unload a lot to allow.')
				end
			end else
				Error_Services('Add', 'RDS is not assigned to a reactor.')
			end

		end else
			Error_Services('Add', 'RDS ' : RDSNo : ' does not exist.')
		end
	end else
		Error_Services('Add', 'Invalid user passed to Unsign Unload Extra 1 routine')
	end
	
end service


Service SignLoadExtra2(RDSNo,LSLUserName)
	
	IF LSLUserName NE ''  AND RowExists('LSL_USERS', LSLUserName) then
		If RowExists('RDS', RDSNo) then
			RDSRec          = Database_Services('ReadDataRow', 'RDS', RDSNo)
			Reactor                     = RDSRec<RDS_REACTOR$>
			If Reactor NE '' then
				CurrReactorLoadCapacity = Reactor_Services('GetReactorAvailChamberCount', Reactor)
				If CurrReactorLoadCapacity GT 0 then
					LoadExtra2Ready = Qa_Services('LoadExtra2ndReady', RDSNo)
					if LoadExtra2Ready then
						RDSRec<RDS_OP_IN_EX3$>      = LSLUsername
						RDSRec<RDS_OP_IN_EX3_DATE$> = SRP_Date('Today')
						RDSRec<RDS_OP_IN_EX3_TIME$> = SRP_Time('Now')
						Database_Services('WriteDataRow', 'RDS', RDSNo, RDSRec, True$, False$, True$)
						If Error_Services('NoError') then
							Rds_Services('AddComment', RDSNo, 'Load Extra 2 Signed to Reactor ' : Reactor, LSLUserName)
						end
						Reactor_Services('AddRDSToReactorLoad', RDSNo, Reactor)
					end else
						Error_Services('Add', 'Load Extra 2 is not able to be signed at this stage')
					end
				end else
					Error_Services('Add', 'Reactor #' : Reactor : ' does not currently have the capacity to be loaded. Unload a lot to allow.')
				end
			end else
				Error_Services('Add', 'RDS is not assigned to a reactor.')
			end
		end else
			Error_Services('Add', 'RDS ' : RDSNo : ' does not exist.')
		end
	end else
		Error_Services('Add', 'Invalid user passed to Sign Unload Extra 1 routine')
	end
	
end service


Service UnsignLoadExtra2(RDSNo, LSLUserName)
	
	IF LSLUserName NE ''  AND RowExists('LSL_USERS', LSLUserName) then
		If RowExists('RDS', RDSNo) then
			RDSRec                = Database_Services('ReadDataRow', 'RDS', RDSNo)
			UnsignLoadExtra2Ready = Qa_Services('UnsignLoadExtra2ndReady', RDSNo)
			if UnsignLoadExtra2Ready then
				RDSRec<RDS_OP_IN_EX3$>      = ''
				RDSRec<RDS_OP_IN_EX3_DATE$> = ''
				RDSRec<RDS_OP_IN_EX3_TIME$> = ''
				Database_Services('WriteDataRow', 'RDS', RDSNo, RDSRec, True$, False$, True$)
				Reactor = RDSRec<RDS_REACTOR$>
				If Error_Services('NoError') then
					Rds_Services('AddComment', RDSNo, 'Load Extra 2 Unsigned on Reactor ' : Reactor, LSLUserName)
				end
				Reactor_Services('RemoveRDSFromReactorLoad', RDSNo, Reactor, LSLUserName)
			end else
				Error_Services('Add', 'Unsign Load Extra 2 is not able to be unsigned.')
			end
		end else
			Error_Services('Add', 'RDS ' : RDSNo : ' does not exist.')
		end
	end else
		Error_Services('Add', 'Invalid user passed to Unsign Unload Extra 1 routine')
	end
	
end service


Service ApplyAbortMetrology(RdsKeys)
	
	Result   = False$
	ErrorMsg = ''
	If RdsKeys NE '' then
		Convert @VM to @FM in RdsKeys
		If RowExists('RDS', RdsKeys) then
			For each RdsKey in RdsKeys using @FM

				WoNo                              = Xlate('RDS', RdsKey, RDS_WO$, 'X')
				// Set a flag on the CI record indicating the run was aborted
				CleanInspKey                      = Xlate('RDS', RdsKey, 'LWI_CI_NO', 'X')
				CIRec                             = Database_Services('ReadDataRow', 'CLEAN_INSP', CleanInspKey)
				CIRec<CLEAN_INSP_WAFERS_REMOVED$> = True$
				Database_Services('WriteDataRow', 'CLEAN_INSP', CleanInspKey, CIRec, True$, False$, False$)
				If Error_Services('HasError') then ErrorMsg = Error_Services('GetMessage')
				
				If ErrorMsg EQ '' then 
					// Change the QA metrology requirements to all wafers
					CassNo                            = Xlate('RDS', RdsKey, 'CASS_NO', 'X')
					WoMatQaKey                        = WoNo : '*' : CassNo
					WoMatQaRec                        = Xlate('WO_MAT_QA', WoMatQaKey, '', 'X')
					WoWaferQty                        = Xlate('WO_MAT', WoMatQaKey, WO_MAT_WAFER_QTY$, 'X')
					WoMatQaStages                     = WoMatQaRec<WO_MAT_QA_STAGE$>
					WoMatQaProfiles                   = WoMatQaRec<WO_MAT_QA_PROFILE$>
					WoMatQaSlots                      = WoMatQaRec<WO_MAT_QA_SLOT$>
					WoMatQaWfrQtys                    = WoMatQaRec<WO_MAT_QA_WFR_QTY$>
					WoMatQaProps                      = WoMatQaRec<WO_MAT_QA_PROP$>
					WoMatQaToolClasses                = WoMatQaRec<WO_MAT_QA_TOOL_CLASS$>
					WoMatQaRecipes                    = WoMatQaRec<WO_MAT_QA_RECIPE$>
					WoMatQaRecipePatterns             = WoMatQaRec<WO_MAT_QA_RECIPE_PATTERN$>
					WoMatQaMin                        = WoMatQaRec<WO_MAT_QA_MIN$>
					WoMatQaMax                        = WoMatQaRec<WO_MAT_QA_MAX$>
					WoMatQaOOS                        = WoMatQaRec<WO_MAT_QA_OUT_OF_SPEC$>
					StageCount                        = DCount(WoMatQaStages, @VM)
					For stageIdx = 1 to StageCount
						If (WoMatQaStages<0, stageIdx> _EQC 'QA') and (WoMatQaProfiles<0, stageIdx> _EQC '1ADE') then
							Slot = WoMatQaSlots<0, stageIdx>
							If (Slot _NEC 'L') then
								WoMatQaSlots<0, stageIdx>   = 'A'
								WoMatQaWfrQtys<0, stageIdx> = WoWaferQty
							end
							If (Slot _EQC 'L') then
								WoMatQaStages<0, stageIdx>         = ''
								WoMatQaProfiles<0, stageIdx>       = ''
								WoMatQaSlots<0, stageIdx>          = ''
								WoMatQaWfrQtys<0, stageIdx>        = ''
								WoMatQaProps<0, stageIdx>          = ''
								WoMatQaToolClasses<0, stageIdx>    = ''
								WoMatQaRecipes<0, stageIdx>        = ''
								WoMatQaRecipePatterns<0, stageIdx> = ''
								WoMatQaMin<0, stageIdx>            = ''
								WoMatQaMax<0, stageIdx>            = ''
								WoMatQaOOS<0, stageIdx>            = ''
							end
						end
					Next stageIdx
					WoMatQaRec<WO_MAT_QA_STAGE$>          = WoMatQaStages
					WoMatQaRec<WO_MAT_QA_PROFILE$>        = WoMatQaProfiles
					WoMatQaRec<WO_MAT_QA_SLOT$>           = WoMatQaSlots
					WoMatQaRec<WO_MAT_QA_WFR_QTY$>        = WoMatQaWfrQtys
					WoMatQaRec<WO_MAT_QA_PROP$>           = WoMatQaProps
					WoMatQaRec<WO_MAT_QA_TOOL_CLASS$>     = WoMatQaToolClasses
					WoMatQaRec<WO_MAT_QA_RECIPE$>         = WoMatQaRecipes
					WoMatQaRec<WO_MAT_QA_RECIPE_PATTERN$> = WoMatQaRecipePatterns
					WoMatQaRec<WO_MAT_QA_MIN$>            = WoMatQaMin
					WoMatQaRec<WO_MAT_QA_MAX$>            = WoMatQaMax
					WoMatQaRec<WO_MAT_QA_OUT_OF_SPEC$>    = WoMatQaOOS
					Database_Services('WriteDataRow', 'WO_MAT_QA', WoMatQaKey, WoMatQaRec, True$, False$, False$)
					If Error_Services('HasError') then ErrorMsg = Error_Services('GetMessage')
				end
				
			Next RdsKey
		end else
			Convert @FM to ',' in RdsKeys
			ErrorMsg = 'Error in ':Service:' service. One or more Rds records (':RdsKeys:') do not exist!'
		end
	end else
		ErrorMsg = 'Error in ':Service:' service. Null RdsKeys passed into service!'
	end
	
	If ErrorMsg NE '' then
		Error_Services('Add', ErrorMsg)
	end else
		Result = True$
	end
	
end service

Service DetachRDSFromWO(RDSNo)
    ErrorMessage = ''
    If RowExists('RDS', RDSNo) then
        RDSRec = Database_Services('ReadDataRow', 'RDS', RDSNo)
        if Error_Services('NoError') then
            RDSRec<RDS_WO$> = ''
            Database_Services('WriteDataRow', 'RDS', RDSNo, RDSRec, False$, False$, False$)
            If Error_Services('HasError') then
                ErrorMessage = Error_Services('GetMessage')
            end
        end else
            ErrorMessage = Error_Services('GetMessage')
        end
    end else
        ErrorMessage = 'Error detaching RDS ' : RDSNo ' from work order. RDS No not found in RDS table.' 
    end
    If ErrorMessage NE ''  then
        Error_Services('Add', ErrorMessage)
    end
end service

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

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













