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 SERVICE_SETUP
$Insert APP_INSERTS
$Insert MSG_EQUATES
$Insert RTI_DEBUG_COMMON
$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
$Insert RDS_LAYER_INFO_EQU
$Insert EPI_PART_EQUATES
$Insert CUST_EPI_PART_EQUATES
$Insert UNIT_EQUATES
$Insert QUOTE_SPEC_EQU

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 PI$LEFT		TO 1
EQU PI$TOP		TO 2
EQU PI$RIGHT	TO 3	
EQU PI$BOTTOM	TO 4
EQU PI$WIDTH	TO 5
EQU PI$HEIGHT	TO 6	
EQU PI$SIZE		TO 7

EQU PS$TOOL		TO 1
EQU PS$TYPE		TO 2
EQU PS$RECIPE	TO 3
EQU PS$FREQ		TO 4
EQU PS$PATTERN	TO 11

EQU COMMA$ to ','

EQU NEW_EXIST$ To 0 ; * Reduce Mode 0
equ NEXT_CUR$  To 1
equ ADD_EXIST$ to 2

Equ NUM_ATTEMPTS$ to 60

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, Delay
Declare subroutine  Rds_Services, Obj_Post_Log, Mona_Services, Transaction_Services, Reduce, Update_Index
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
Declare function    obj_Vendor_Code

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$
	// 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)
	
	ReactorType = Xlate('RDS', RDSNo, 'PS_REACTOR_TYPE', 'X')
	IsEpiPro = ( (ReactorType _EQC 'EPP') or (ReactorType _EQC 'P') or (ReactorType _EQC 'EpiPro') )
	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)

	// Note: We need to use the Quote function before comparing values because OpenInsight
	//		 fails to compare strings that start with numbers.
	IsValid = False$
	Begin Case
		
		Case RDSType EQ 'SIC'
			CompareLot = Xlate('RDS', RDSNo, RDS_LOT_NUM$, 'X')
			If Quote(CompareLot) EQ Quote(SupplierLot) then IsValid = True$
			
		Case RDSType EQ 'EPP'
			Convert '.' to '*' in RDSNo
			CompareLot = Xlate('WM_IN', RDSNo, 'LOT_NO', 'X')
			If Quote(CompareLot) EQ Quote(SupplierLot) then IsValid = True$
			
		Case RDSType EQ 'GAN'
			Convert '.' to '*' in RDSNo
			CompareLot = Xlate('WO_MAT', RDSNo, WO_MAT_LOT_NO$, 'X')
			If Quote(CompareLot) EQ Quote(SupplierLot) then IsValid = True$
			
	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
						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
						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'
	
	ErrMsg = ''
	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
				Until ErrMsg NE ''
					// Copy RDS layer records
					LayerID = Field(CopyRDSLayerKey, '*', 2)
					DestRDSLayerKey = RDSNo:'*':LayerID
					CopyRDSLayerRec = Database_Services('ReadDataRow', 'RDS_LAYER', CopyRDSLayerKey)
					if Error_Services('NoError') then
						DestRDSLayerRec = Database_Services('ReadDataRow', 'RDS_LAYER', DestRDSLayerKey)
						If Error_Services('NoError') then
							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$)
							If Error_Services('HasError') then
								ErrMsg = Error_Services('GetMessage')
							end
						end else
							ErrMsg = Error_Services('GetMessage')
						end
					end else
						ErrMsg = Error_Services('GetMessage')
					end
				Next CopyRDSLayerKey
			end
		end else
			ErrMsg = 'No previous run to copy RDS layer parameters from in ':Service:' service.'
		end
	end else
		ErrMsg = 'RDSNo not supplied in ':Service:' service.'
	end
	
	EndTick = GetTickCount()
    Mona_Services('QueueLatencyAndCountMetrics', MonaResource, MetricName, StartTick, EndTick)
    
    If ErrMsg NE '' then Error_Services('Add', ErrMsg)
    
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)
	
	ErrorMsg = ''
	
	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$, False$)
				If Error_Services('NoError') then
					Reactor                      = RDSRec<RDS_REACTOR$>
				    Reactor_Services('RemoveRDSFromReactorLoad', RDSNo, Reactor, LSLUserName)
				end else
					ErrorMsg = Error_Services('GetMessage')
				end
			end else
				ErrorMsg = 'Unload Extra 1 is not able to be signed at this stage'
			end
		end else
			ErrorMsg = 'RDS ' : RDSNo : ' does not exist.'
		end
	end else
		ErrorMsg = 'Invalid user passed to Sign Unload Extra 1 routine'
	end
	
	If ErrorMsg NE '' then
		Error_Services('Add', 'Error signing Unload Extra 1 : ' : ErrorMsg)
	end
	
end service


Service UnsignUnloadExtra1(RDSNo, LSLUserName)
	
	ErrorMsg = ''
	
	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$> = ''
						Reactor                      = RDSRec<RDS_REACTOR$>
						Database_Services('WriteDataRow', 'RDS', RDSNo, RDSRec, True$, False$, False$)
						If Error_Services('NoError') then
						    Reactor_Services('AddRDSToReactorLoad', RDSNo, Reactor)
						end else
							ErrorMsg = Error_Services('GetMessage')
						end
					end else
						ErrorMsg = 'Unsign Load Extra 1 is not able to be unsigned.'
					end
				end else
					ErrorMsg = 'Reactor #' : Reactor : ' does not currently have the capacity to be loaded. Unload a lot to allow.'
				end
			end else
				ErrorMsg = 'RDS is not assigned to a reactor.'
			end

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


Service SignLoadExtra1(RDSNo,LSLUserName)
	
	ErrorMsg = ''
	
	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$, False$)
						If Error_Services('NoError') then
						    Reactor_Services('AddRDSToReactorLoad', RDSNo, Reactor)
						end else
							ErrorMsg = Error_Services('GetMessage')
						end
					end else
						ErrorMsg = 'Load Extra 1 is not able to be signed at this stage'
					end
				end else
					ErrorMsg = 'Reactor #' : Reactor : ' does not currently have the capacity to be loaded. Unload a lot to allow.'
				end
			end else
				ErrorMsg = 'RDS is not assigned to a reactor.'
			end
			
		end else
			ErrorMsg = 'RDS ' : RDSNo : ' does not exist.'
		end
	end else
		ErrorMsg = 'Invalid user passed to Sign Unload Extra 1 routine'
	end
	
	If ErrorMsg NE '' then
		Error_Services('GetMessage')
	end
	
end service


Service UnsignLoadExtra1(RDSNo, LSLUserName)
	
	ErrorMsg = ''
	
	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$> = ''
				Reactor                     = RDSRec<RDS_REACTOR$>
				Database_Services('WriteDataRow', 'RDS', RDSNo, RDSRec, True$, False$, False$)
				If Error_Services('NoError') then
				    Reactor_Services('RemoveRDSFromReactorLoad', RDSNo, Reactor, LSLUserName)
				end else
					ErrorMsg = Error_Services('GetMessage')
				end
			end else
				ErrorMsg = 'Unsign Load Extra 1 is not able to be unsigned.'
			end
		end else
			ErrorMsg = 'RDS ' : RDSNo : ' does not exist.'
		end
	end else
		ErrorMsg = 'Invalid user passed to Unsign Load Extra 1 routine'
	end
	
	If ErrorMsg NE '' then
		Error_Services('Add', ErrorMsg)
	end
	
end service


Service SignUnloadExtra2(RDSNo,LSLUserName)
	
	ErrorMsg = ''
	
	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$, False$)
				If Error_Services('NoError') then
					Reactor                      = RDSRec<RDS_REACTOR$>
				    Reactor_Services('RemoveRDSFromReactorLoad', RDSNo, Reactor, LSLUserName)
				end else
					ErrorMsg = Error_Services('GetMessage')
				end
			end else
				ErrorMsg = 'Unload Extra 2 is not able to be signed at this stage'
			end
		end else
			ErrorMsg =  'RDS ' : RDSNo : ' does not exist.'
		end
	end else
		ErrorMsg = 'Invalid user passed to Sign Unload Extra 1 routine'
	end
	
	If ErrorMsg NE '' then
		Error_Services('Add', ErrorMsg)
	end
	
end service


Service UnsignUnloadExtra2(RDSNo, LSLUserName)
	
	ErrorMsg = ''
	
	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$> = ''
						Reactor = RDSRec<RDS_REACTOR$>
						Database_Services('WriteDataRow', 'RDS', RDSNo, RDSRec, True$, False$, False$)
						If Error_Services('NoError') then
						    Reactor_Services('AddRDSToReactorLoad', RDSNo, Reactor)
						end else
							ErrorMsg = Error_Services('GetMessage')
						end
					end else
						ErrorMsg = 'Unsign Load Extra 2 is not able to be unsigned.'
					end
				end else
					ErrorMsg = 'Reactor #' : Reactor : ' does not currently have the capacity to be loaded. Unload a lot to allow.'
				end
			end else
				ErrorMsg =  'RDS is not assigned to a reactor.'
			end

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


Service SignLoadExtra2(RDSNo,LSLUserName)
	
	ErrorMsg = ''
	
	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$, False$)
						If Error_Services('NoError') then
						    Reactor_Services('AddRDSToReactorLoad', RDSNo, Reactor)
						end else
							ErrorMsg = Error_Services('GetMessage')
						end
					end else
						ErrorMsg = 'Load Extra 2 is not able to be signed at this stage'
					end
				end else
					ErrorMsg = 'Reactor #' : Reactor : ' does not currently have the capacity to be loaded. Unload a lot to allow.'
				end
			end else
				ErrorMsg = 'RDS is not assigned to a reactor.'
			end
		end else
			ErrorMsg = 'RDS ' : RDSNo : ' does not exist.'
		end
	end else
		ErrorMsg = 'Invalid user passed to Sign Unload Extra 2 routine'
	end
	
	If ErrorMsg NE '' then
		Error_Services('Add', ErrorMsg)
	end
	
end service


Service UnsignLoadExtra2(RDSNo, LSLUserName)
	
	ErrorMsg = ''
	
	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$> = ''
				Reactor = RDSRec<RDS_REACTOR$>
				Database_Services('WriteDataRow', 'RDS', RDSNo, RDSRec, True$, False$, False$)
				If Error_Services('NoError') then
				    Reactor_Services('RemoveRDSFromReactorLoad', RDSNo, Reactor, LSLUserName)
				end else
					ErrorMsg = Error_Services('GetMessage')
				end
			end else
				ErrorMsg = 'Unsign Load Extra 2 is not able to be unsigned.'
			end
		end else
			ErrorMsg = 'RDS ' : RDSNo : ' does not exist.'
		end
	end else
		ErrorMsg = 'Invalid user passed to Unsign Unload Extra 1 routine'
	end
	
	If ErrorMsg NE '' then
		Error_Services('Add', ErrorMsg)
	end
	
end service


Service ApplyQA100PercentADE(RDSNo)
    
    Result = False$
    ErrorMsg = ''
    If RDSNo NE '' then
        // Change the QA metrology requirements to all wafers
        WoNo                              = Xlate('RDS', RDSNo, RDS_WO$, 'X')
        CassNo                            = Xlate('RDS', RDSNo, 'CASS_NO', 'X')
        WoMatQaKey                        = WoNo : '*' : CassNo
        WoMatQaRec                        = Xlate('WO_MAT_QA', WoMatQaKey, '', 'X')
        EpiCheck = Rds_Services('IsEpiPro', RDSNo)
        If EpiCheck EQ True$ then 
            WoWaferQty                    = Xlate('WO_MAT', WoMatQaKey, WO_MAT_WAFER_QTY$, 'X')
        end else
            WoWaferQty                    = Xlate('RDS', RDSNo, 'WFRS_OUT', 'X')
        end
        
        WoMatQaStages         = WoMatQaRec<WO_MAT_QA_STAGE$>
        WoMatQaProfiles       = WoMatQaRec<WO_MAT_QA_PROFILE$>
        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$>
        WoMatQaSlots          = WoMatQaRec<WO_MAT_QA_SLOT$>       
        WoMatQaSlotTests      = WoMatQaRec<WO_MAT_QA_SLOT_TEST$>
        WoMatQaResults        = WoMatQaRec<WO_MAT_QA_RESULT$>
        WoMatQaStdMaxes       = WoMatQaRec<WO_MAT_QA_STD_MAX$>
        WoMatQaStdResults     = WoMatQaRec<WO_MAT_QA_STD_RESULT$>
        WoMatQaSigs           = WoMatQaRec<WO_MAT_QA_SIG$>
        WoMatQaSigDtms        = WoMatQaRec<WO_MAT_QA_SIG_DTM$>
        WoMatQaWfrQtys        = WoMatQaRec<WO_MAT_QA_WFR_QTY$>
        WoMatQaWfrTypes       = WoMatQaRec<WO_MAT_QA_WFR_TYPE$>
        WoMatQaReactScheds    = WoMatQaRec<WO_MAT_QA_REACT_SCHED$>
        WoMatQaShipDocs       = WoMatQaRec<WO_MAT_QA_SHIP_DOC$>
        WoMatQaDataPoints     = WoMatQaRec<WO_MAT_QA_DATA_POINTS$>
        WoMatQaMinResults     = WoMatQaRec<WO_MAT_QA_MIN_RESULT$>
        WoMatQaMaxResults     = WoMatQaRec<WO_MAT_QA_MAX_RESULT$>
        WoMatQaRngResults     = WoMatQaRec<WO_MAT_QA_RANGE_PCT_RESULT$>
        WoMatQaEdgeResults    = WoMatQaRec<WO_MAT_QA_EDGE_MEAN_RESULT$>
        WoMatQa5mmResults     = WoMatQaRec<WO_MAT_QA_5MM_PCT_RESULT$>
        WoMatQaOOS            = WoMatQaRec<WO_MAT_QA_OUT_OF_SPEC$>
        WoMatQaPhaseMins      = WoMatQaRec<WO_MAT_QA_PHASE_MIN$>
        WoMatQaFailReasons    = WoMatQaRec<WO_MAT_QA_FAIL_REASON$>
        
        StageCount         = DCount(WoMatQaStages, @VM)
        StageIdxToDelete = ''
        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
                    WoMatQaSigs<0, stageIdx>        = ''
                    WoMatQaSigDtms<0, stageIdx>     = ''
                    WoMatQaResults<0, stageIdx>     = ''
                    WoMatQaDataPoints<0, stageIdx>  = ''
                    WoMatQaMinResults<0, stageIdx>  = ''
                    WoMatQaMaxResults<0, stageIdx>  = ''
                    WoMatQaRngResults<0, stageIdx>  = ''
                    WoMatQaEdgeResults<0, stageIdx> = ''
                    WoMatQa5mmResults<0, stageIdx>  = ''
                end
                If (Slot _EQC 'L') then
                	StageIdxToDelete = stageIdx
                end
            end
        Next stageIdx
        
        If (StageIdxToDelete NE '') then
			WoMatQaStages         = Delete(WoMatQaStages,   	  0, StageIdxToDelete, 0)
			WoMatQaProfiles       = Delete(WoMatQaProfiles, 	  0, StageIdxToDelete, 0)
			WoMatQaProps          = Delete(WoMatQaProps,		  0, StageIdxToDelete, 0)
			WoMatQaToolClasses    = Delete(WoMatQaToolClasses,    0, StageIdxToDelete, 0)
			WoMatQaRecipes        = Delete(WoMatQaRecipes,		  0, StageIdxToDelete, 0)
			WoMatQaRecipePatterns = Delete(WoMatQaRecipePatterns, 0, StageIdxToDelete, 0)
			WoMatQaMin            = Delete(WoMatQaMin,  		  0, StageIdxToDelete, 0)
			WoMatQaMax            = Delete(WoMatQaMax,  		  0, StageIdxToDelete, 0)
			WoMatQaSlots          = Delete(WoMatQaSlots,		  0, StageIdxToDelete, 0)
			WoMatQaSlotTests      = Delete(WoMatQaSlotTests,	  0, StageIdxToDelete, 0)
			WoMatQaResults        = Delete(WoMatQaResults,  	  0, StageIdxToDelete, 0)
			WoMatQaSigs           = Delete(WoMatQaSigs,  		  0, StageIdxToDelete, 0)
			WoMatQaSigDtms        = Delete(WoMatQaSigDtms,		  0, StageIdxToDelete, 0)
			WoMatQaStdMaxes       = Delete(WoMatQaStdMaxes,  	  0, StageIdxToDelete, 0)
			WoMatQaStdResults     = Delete(WoMatQaStdResults,  	  0, StageIdxToDelete, 0)
			WoMatQaWfrQtys        = Delete(WoMatQaWfrQtys,  	  0, StageIdxToDelete, 0)
			WoMatQaWfrTypes       = Delete(WoMatQaWfrTypes,  	  0, StageIdxToDelete, 0)
			WoMatQaReactScheds    = Delete(WoMatQaReactScheds,    0, StageIdxToDelete, 0)
			WoMatQaShipDocs       = Delete(WoMatQaShipDocs,  	  0, StageIdxToDelete, 0)
			WoMatQaDataPoints     = Delete(WoMatQaDataPoints,  	  0, StageIdxToDelete, 0)
			WoMatQaMinResults     = Delete(WoMatQaMinResults, 	  0, StageIdxToDelete, 0)
			WoMatQaMaxResults     = Delete(WoMatQaMaxResults,  	  0, StageIdxToDelete, 0)
			WoMatQaRngResults     = Delete(WoMatQaRngResults,  	  0, StageIdxToDelete, 0)
			WoMatQaEdgeResults    = Delete(WoMatQaEdgeResults, 	  0, StageIdxToDelete, 0)
			WoMatQa5mmResults     = Delete(WoMatQa5mmResults,  	  0, StageIdxToDelete, 0)
			WoMatQaOOS            = Delete(WoMatQaOOS,  		  0, StageIdxToDelete, 0)
			WoMatQaPhaseMins      = Delete(WoMatQaPhaseMins,  	  0, StageIdxToDelete, 0)
			WoMatQaFailReasons    = Delete(WoMatQaFailReasons,	  0, StageIdxToDelete, 0)
        end
        
    	WoMatQaRec<WO_MAT_QA_STAGE$>            = WoMatQaStages
        WoMatQaRec<WO_MAT_QA_PROFILE$>          = WoMatQaProfiles
        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_SLOT$>             = WoMatQaSlots
        WoMatQaRec<WO_MAT_QA_SLOT_TEST$>        = WoMatQaSlotTests
        WoMatQaRec<WO_MAT_QA_RESULT$>           = WoMatQaResults
        WoMatQaRec<WO_MAT_QA_SIG$>              = WoMatQaSigs
        WoMatQaRec<WO_MAT_QA_SIG_DTM$>          = WoMatQaSigDtms
    	WoMatQaRec<WO_MAT_QA_STD_MAX$>          = WoMatQaStdMaxes
        WoMatQaRec<WO_MAT_QA_STD_RESULT$>       = WoMatQaStdResults
        WoMatQaRec<WO_MAT_QA_WFR_QTY$>          = WoMatQaWfrQtys
        WoMatQaRec<WO_MAT_QA_WFR_TYPE$>         = WoMatQaWfrTypes
        WoMatQaRec<WO_MAT_QA_REACT_SCHED$>      = WoMatQaReactScheds
        WoMatQaRec<WO_MAT_QA_SHIP_DOC$>         = WoMatQaShipDocs
        WoMatQaRec<WO_MAT_QA_DATA_POINTS$>      = WoMatQaDataPoints
        WoMatQaRec<WO_MAT_QA_MIN_RESULT$>       = WoMatQaMinResults
        WoMatQaRec<WO_MAT_QA_MAX_RESULT$>       = WoMatQaMaxResults
        WoMatQaRec<WO_MAT_QA_RANGE_PCT_RESULT$> = WoMatQaRngResults
        WoMatQaRec<WO_MAT_QA_EDGE_MEAN_RESULT$> = WoMatQaEdgeResults
        WoMatQaRec<WO_MAT_QA_5MM_PCT_RESULT$>   = WoMatQa5mmResults
        WoMatQaRec<WO_MAT_QA_OUT_OF_SPEC$>      = WoMatQaOOS
        WoMatQaRec<WO_MAT_QA_PHASE_MIN$>        = WoMatQaPhaseMins
        WoMatQaRec<WO_MAT_QA_FAIL_REASON$>      = WoMatQaFailReasons
        
        Database_Services('WriteDataRow', 'WO_MAT_QA', WoMatQaKey, WoMatQaRec, True$, False$, False$)
        If Error_Services('HasError') then ErrorMsg = Error_Services('GetMessage')
    end else
		ErrorMsg = 'Error in ':Service:' service. Null RdsKey passed into service!'
	end
	
	If ErrorMsg NE '' then
		Error_Services('Add', ErrorMsg)
	end else
		Result = True$
	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
				    Result = Rds_Services('ApplyQA100PercentADE', RDSKey)
					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$> = ''
            RDSRec<RDS_WO_STEP_KEY$> = ''
            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

Service ChangeReactor(RDSNo, NewReactorNo)
    
    ErrorMessage = ''
    If RDSNo NE '' AND  NewReactorNo NE '' then
        RDSRec  = Database_Services('ReadDataRow', 'RDS', RDSNo)
        WONo            = RDSRec<RDS_WO$>
        Query         = 'SELECT REACT_STATE WITH SCHED_WO CONTAINING ':WONo
        ClearSelect
        Set_Status(0)
        RList(Query, TARGET_ACTIVELIST$, '', '' '')
        SchedReactors = ''
        EOF           = False$
        Loop
            ReadNext SchedReactor else EOF = True$
        Until EOF
            SchedReactors<0, -1> = SchedReactor
        Repeat
        
        Locate NewReactorNo in SchedReactors using @VM setting vPos then
            RDSRec<RDS_REACTOR$> = NewReactorNo
            Database_Services('WriteDataRow', 'RDS', RDSNo, RDSRec, True$, False$, False$)
            If Error_Services('NoError') then
                Response = True$
            end else
                Response     = False$
                ErrorMessage = Error_Services('GetMessage')
            end
        end else
            Swap @VM with ',' in SchedReactors
            ErrorMessage = 'Assigned reactor, ':NewReactorNo:', does not equal any scheduled reactor(s), ':SchedReactors:'.'
            Response     = False$
        end
	end else
	    ErrorMessage = 'Missing RDSNo or NewReactorNo param.'
	end
    
    If ErrorMessage NE '' then
        Error_Services('Set', ErrorMessage)
    end
    
end service

Service PushSigProfileToWoMat(RDSNo)
	
	ErrorMsg = '' 
	If (RDSNo NE '') then
		If RowExists('RDS', RDSNo) then
			Record = Database_Services('ReadDataRow', 'RDS', RDSNo)
			If Error_Services('NoError') then
				WONo      = Record<RDS_WO$>
				ReactType = Xlate('WO_LOG', WONo, 'REACT_TYPE', 'X')
				EpiPro    = (ReactType EQ 'EPP')
				GaN       = (ReactType EQ 'GAN')
				NonEpiPro = ( (ReactType NE 'EPP') and (ReactType NE 'GAN') )
				If NonEpiPro then
					// Sync up VER, LOAD, UNLOAD, POST, and QA signatures with WO_MAT signature profile.
					// The goal of signature services was to remove any dependency on the WO_MAT signature profile,
					// however Fabtime relies on this data being replicated to the Scrape SQL DB. 
					WOMatKey = Xlate('RDS', RDSNo, 'WO_MAT_KEY', 'X')
					If WOMatKey NE '' then
						WOMatRec = Database_Services('ReadDataRow', 'WO_MAT', WOMatKey)
						If Error_Services('NoError') then 
							WOMatSigProf = obj_WO_Mat('CassSigProfile', WOMatKey)
							WOMatSig     = WOMatRec<WO_MAT_SIGNATURE$>
							WOMatSigDTM  = WOMatRec<WO_MAT_SIG_DTM$>
							WOMatSigProfOrig = WOMatSigProf
							WOMatSigOrig     = WOMatSig
							WOMatSigDTMOrig  = WOMatSigDTM

							// Update WO_MAT signature profile to match for the "1VER" stage
							Stage = '1VER'
							Locate Stage in WOMatSigProf using @VM setting vPos then
								WOMatSig<0, vPos> = Record<RDS_PRE_EPI_SIG$>
								PreEpiDate        = Record<RDS_PRE_EPI_SIG_DATE$>
								If PreEpiDate NE '' then
									PreEpiTime   = Record<RDS_PRE_EPI_SIG_TIME$>
									PreEpiSigDTM = PreEpiDate + (PreEpiTime/86400)
								end else
									PreEpiSigDTM = ''
								end
								WOMatSigDTM<0, vPos> = PreEpiSigDTM
							end
							
							// Update WO_MAT signature profile to match for "1LOAD" stage
							Stage = '1LOAD'
							Locate Stage in WOMatSigProf using @VM setting vPos then
								WOMatSig<0, vPos> = Record<RDS_OPERATOR_IN$>
								DateIn            = Record<RDS_DATE_IN$>
								If DateIn NE '' then
									LoadTime   = Record<RDS_TIME_IN$>
									LoadSigDTM = DateIn + (LoadTime/86400)
								end else
									LoadSigDTM = ''
								end
								WOMatSigDTM<0, vPos> = LoadSigDTM
							end
							
							// Update WO_MAT signature profile to match for the "1UNLOAD" stage
							Stage = '1UNLOAD'
							Locate Stage in WOMatSigProf using @VM setting vPos then
								WOMatSig<0, vPos> = Record<RDS_OPERATOR_OUT$>
								DateOut           = Record<RDS_DATE_OUT$>
								If DateOut NE '' then
									TimeOut       = Record<RDS_TIME_OUT$>
									TimeOutSigDTM = DateOut + (TimeOut/86400)
								end else
									TimeOutSigDTM = ''
								end
								WOMatSigDTM<0, vPos> = TimeOutSigDTM
							end
							
							// Update WO_MAT signature profile to match for the "1POST" stage
							Stage = '1POST'
							Locate Stage in WOMatSigProf using @VM setting vPos then
								WOMatSig<0, vPos> = Record<RDS_POST_EPI_SIG$>
								PostSigDate       = Record<RDS_POST_EPI_SIG_DATE$>
								If PostSigDate NE '' then
									PostSigTime = Record<RDS_POST_EPI_SIG_TIME$>
									PostSigDTM  = PostSigDate + (PostSigTime/86400)
								end else
									PostSigDTM = ''
								end
								WOMatSigDTM<0, vPos> = PostSigDTM
							end   
							
							// Update WO_MAT signature profile to match for the "1QA" stage
							Stage = '1QA'
							Locate Stage in WOMatSigProf using @VM setting vPos then
								WOMatSig<0, vPos> = Record<RDS_SUP_VER_SIG$>
								FQADate           = Record<RDS_SUP_VER_SIG_DATE$>
								If FQADate NE '' then
									FQASigTime = Record<RDS_SUP_VER_SIG_TIME$>
									FQASigDTM  = FQADate + (FQASigTime/86400)
								end else
									FQASigDTM = ''
								end
								WOMatSigDTM<0, vPos> = FQASigDTM
							end        
							
							If WOMatSigProf NE WOMatSigProfOrig then
								Transaction_Services('PostWriteFieldTransaction', 'WO_MAT', WOMatKey, WO_MAT_SIG_PROFILE$, WOMatSigProf)
							end
							If WOMatSig NE WOMatSigOrig then
								Transaction_Services('PostWriteFieldTransaction', 'WO_MAT', WOMatKey, WO_MAT_SIGNATURE$, WOMatSig)
							end
							If WOMatSigDTM NE WOMatSigDTMOrig then
								Transaction_Services('PostWriteFieldTransaction', 'WO_MAT', WOMatKey, WO_MAT_SIG_DTM$, WOMatSigDTM)
							end

						end else
							ErrorMsg = Error_Services('GetMessage')
						end
					end else
						ErrorMsg = 'Error in ':Service:' service. Null WO_MAT_KEY returned for RDS ':RDSNo:'.'
					end
				end
			end else
				ErrorMsg = Error_Services('GetMessage')
			end
		end else
			ErrorMsg = 'Error in ':Service:' service. RDS ':RDSNo:' does not exist.'
		end
	end else
		ErrorMsg = 'Error in ':Service:' service. Null RDSNo passed into service.'
	end
    
    If ErrorMsg NE '' then Error_Services('Add', ErrorMsg)
	
end service

Service AllTWUseSigned(RDSNo)
	If RDSNo NE '' then
		LSKeys = Xlate('RDS', RDSNo, 'RDS_LS_SORTED', 'X')
		AllMetKeys = XLATE('RDS_LAYER', LSKeys, RDS_LAYER_RDS_TEST_KEYS$, 'X')
		AllSigned = 1
		FOR I = 1 TO COUNT(AllMetKeys,@VM) + (AllMetKeys NE '')
			MetKey = AllMetKeys<1,I>
			AllSigned = obj_RDS_Test('TWSignedOff',MetKey)
		UNTIL NOT(AllSigned)
		NEXT I
		
		Transaction_Services('PostWriteFieldTransaction', 'RDS', RDSNo, RDS_ALL_TW_USE_SIGNED$, AllSigned)
		
		ErrorMessage = ''
		If Error_Services('HasError') then
			ErrorMessage = Error_Services('GetMessage')
		end
		
		If ErrorMessage NE '' then
			ErrorMessage = 'Unable to determine if all TW Use have been signed for RDS ':RDSNo:', because ':ErrorMessage
			Error_Services('Add', ErrorMessage)
		end
	end
end service

Service GetRDSKeys(WONo)
    
    Response = ''
    ErrorMsg = ''
    If (WONo NE '') then
        If RowExists('WO_LOG', WONo) then
			TableName    = 'RDS'
			SortList     = 'RUN_ORDER_NUM'
			ReduceScript = 'WITH {WO} EQ ':Quote(WONo)
			Mode         = NEXT_CUR$
			Flag         = ''
			Cursor       = ''
			GoSub ClearCursors
			Reduce(ReduceScript, SortList, Mode, TableName, Cursor, Flag)
			If Flag then
				Select TableName by SortList using Cursor then
					EOF = False$
					Loop
						Readnext KeyId using Cursor by AT else EOF = True$
					Until EOF
						Response<0, -1> = KeyId
					Repeat
				end else
					ErrorMsg = 'Error in ':Service:' service. Error selecting ':TableName:' table.'
				end
			end else
				ErrorMsg = 'Error in ':Service:' service. Error calling Reduce'
			end
        end else
            ErrorMsg = 'Error in ':Service:' service. WO_LOG ':WONo:' does not exist.'
        end
    end else
        ErrorMsg = 'Error in ':Service:' service. Null WONo passed into service.'
    end
    
    If ErrorMsg NE '' then Error_Services('Add', ErrorMsg)
    
end service


Service VerifyWOMatRDSNoIndex(RDSNo)
    
    LogPath           = Environment_Services('GetApplicationRootPath') : '\LogFiles\RDS'
    LogDate           = Oconv(Date(), 'D4/')
    LogTime           = Oconv(Time(), 'MTS')
    LogFileName       = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' !RDS WO_MAT{RDS_NO} Log.csv'
    Headers           = 'Logging DTM':@FM:'RDSNo':@FM:'WOMatKey':@FM:'Result'
    objVerifyRDSNoLog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, COMMA$, Headers, '', False$, False$)
    
    LogData    = ''
    LogData<1> = OConv(Datetime(), 'DT/^S')
    LogData<2> = RDSNo
    LogData<4> = 'Begin ':Service
    Logging_Services('AppendLog', objVerifyRDSNoLog, LogData, @RM, @FM)
    
    ErrorMsg = ''
    If RDSNo NE '' then
        If RowExists('RDS', RDSNo) then
            WOMatKey = Xlate('RDS', RDSNo, 'WO_MAT_KEY', 'X')
            If WOMatKey NE '' then
                WOMatRDSNo = Xlate('WO_MAT', WOMatKey, 'RDS_NO', 'X')
                LogData<1> = OConv(Datetime(), 'DT/^S')
                LogData<3> = WOMatKey
                Logging_Services('AppendLog', objVerifyRDSNoLog, LogData, @RM, @FM)               
                If WOMatRDSNo EQ '' then
                    LogData<1> = OConv(Datetime(), 'DT/^S')
                    LogData<4>          = 'RDSNo missing from WO_MAT record. Generating index transaction.'
                    Logging_Services('AppendLog', objVerifyRDSNoLog, LogData, @RM, @FM)     
                    // Add index transaction to update RDS_NO relational index (target WO_MAT table)
                    IndexTransactionRow = 'WO_MAT*RDS_NO*AR':@FM:RDSNo:@FM:"":@FM:WOMatKey:@FM
                    Open "!RDS" to BangTable then
                        Done = False$
                        For AttemptNo = 1 to NUM_ATTEMPTS$
                            If AttemptNo GT 1 then Delay(1)
                            Lock BangTable, 0 then
                                Read PendingTrans from BangTable, 0 else PendingTrans = '0':@FM
                                PendingTrans := IndexTransactionRow
                                Write PendingTrans on BangTable, 0 then
                                    LogData<1> = OConv(Datetime(), 'DT/^S')
                                    LogData<4> = 'Index transaction successfully added.'
                                    Logging_Services('AppendLog', objVerifyRDSNoLog, LogData, @RM, @FM)
                                    Done       = True$
                                end else
                                    ErrorMsg = 'Unable to write index transaction to !RDS. ':RDSNo
                                end
                                Unlock BangTable, 0 else ErrorMsg = 'Unable to Open !RDS to add index transaction. ':RDSNo
                            end else
                                If AttemptNo GE NUM_ATTEMPTS$ then 
                                    ErrorMsg = 'Unable to Lock !RDS to add index transaction. ':RDSNo
                                end
                            end
                        Until Done or ErrorMsg
                        Next AttemptNo
                    end else
                        ErrorMsg = 'Unable to Open !RDS to add index transaction. ':RDSNo
                    end
                end else
                    LogData<1> = OConv(Datetime(), 'DT/^S')
                    LogData<4> = 'Index ok. RDSNo not missing from WO_MAT record.'
                    Logging_Services('AppendLog', objVerifyRDSNoLog, LogData, @RM, @FM)                         
                end
            end else
                LogData<1> = OConv(Datetime(), 'DT/^S')
                LogData<4> = 'WO_MAT key for RDS ':RDSNo:' is null. Nothing to update.'
                Logging_Services('AppendLog', objVerifyRDSNoLog, LogData, @RM, @FM)                    
            end
        end
    end
    
    If ErrorMsg NE '' then
        LogData<1> = OConv(Datetime(), 'DT/^S')
        LogData<4> = ErrorMsg
        Logging_Services('AppendLog', objVerifyRDSNoLog, LogData, @RM, @FM)
    end
    
    LogData<1> = OConv(Datetime(), 'DT/^S')
    LogData<4> = 'End ':Service
    Logging_Services('AppendLog', objVerifyRDSNoLog, LogData, @RM, @FM)
    
    If ErrorMsg NE '' then Error_Services('Add', ErrorMsg)
    
end service


Service VerifyWOStepRDSKeyIndex(RDSNo)
    
    LogPath            = Environment_Services('GetApplicationRootPath') : '\LogFiles\RDS'
    LogDate            = Oconv(Date(), 'D4/')
    LogTime            = Oconv(Time(), 'MTS')
    LogFileName        = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' !RDS WO_STEP{RDS_KEY} Log.csv'
    Headers            = 'Logging DTM':@FM:'RDSNo':@FM:'WOStep':@FM:'Result'
    objVerifyRDSKeyLog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, COMMA$, Headers, '', False$, False$)
    
    LogData    = ''
    LogData<1> = OConv(Datetime(), 'DT/^S')
    LogData<2> = RDSNo
    LogData<4> = 'Begin ':Service
    Logging_Services('AppendLog', objVerifyRDSKeyLog, LogData, @RM, @FM)
    
    ErrorMsg = ''
    If RDSNo NE '' then
        If RowExists('RDS', RDSNo) then
            WOStepKey = Xlate('RDS', RDSNo, 'WO_STEP_KEY', 'X')
            If WOStepKey NE '' then
                WOStepRDSKeys = Xlate('WO_STEP', WOStepKey, 'RDS_KEY', 'X')
                LogData<1> = OConv(Datetime(), 'DT/^S')
                LogData<3>    = WOStepKey
                Logging_Services('AppendLog', objVerifyRDSKeyLog, LogData, @RM, @FM)
                Locate RDSNo in WOStepRDSKeys using @VM setting vPos then
                    LogData<1> = OConv(Datetime(), 'DT/^S')
                    LogData<4> = 'Index ok. RDSNo not missing from WO_STEP record.'
                    Logging_Services('AppendLog', objVerifyRDSKeyLog, LogData, @RM, @FM)
                end else
                    LogData<1> = OConv(Datetime(), 'DT/^S')
                    LogData<4>          = 'RDSNo missing from WO_STEP record. Generating index transaction.'
                    Logging_Services('AppendLog', objVerifyRDSKeyLog, LogData, @RM, @FM)     
                    // Add index transaction to update RDS_KEY relational index (target WO_STEP table)
                    IndexTransactionRow = 'WO_STEP*RDS_KEY*AR':@FM:RDSNo:@FM:"":@FM:WOStepKey:@FM
                    Open "!RDS" to BangTable then
                        Done = False$
                        For AttemptNo = 1 to NUM_ATTEMPTS$
                            If AttemptNo GT 1 then Delay(1)
                            Lock BangTable, 0 then
                                Read PendingTrans from BangTable, 0 else PendingTrans = '0':@FM
                                PendingTrans := IndexTransactionRow
                                Write PendingTrans on BangTable, 0 then
                                    LogData<1> = OConv(Datetime(), 'DT/^S')
                                    LogData<4> = 'Index transaction successfully added.'
                                    Logging_Services('AppendLog', objVerifyRDSKeyLog, LogData, @RM, @FM)
                                    Done       = True$
                                end else
                                    ErrorMsg = 'Unable to write index transaction to !RDS. ':RDSNo
                                end
                                Unlock BangTable, 0 else ErrorMsg = 'Unable to Open !RDS to add index transaction. ':RDSNo
                            end else
                                If AttemptNo GE NUM_ATTEMPTS$ then 
                                    ErrorMsg = 'Unable to Lock !RDS to add index transaction. ':RDSNo
                                end
                            end
                        Until Done or ErrorMsg
                        Next AttemptNo
                    end else
                        ErrorMsg = 'Unable to Open !RDS to add index transaction. ':RDSNo
                    end
                end
            end else
                LogData<1> = OConv(Datetime(), 'DT/^S')
                LogData<4> = 'WO_STEP key for RDS ':RDSNo:' is null. Nothing to update.'
                Logging_Services('AppendLog', objVerifyRDSKeyLog, LogData, @RM, @FM)                    
            end
        end
    end
    
    If ErrorMsg NE '' then
        LogData<1> = OConv(Datetime(), 'DT/^S')
        LogData<4> = ErrorMsg
        Logging_Services('AppendLog', objVerifyRDSKeyLog, LogData, @RM, @FM)
    end
    
    LogData<1> = OConv(Datetime(), 'DT/^S')
    LogData<4> = 'End ':Service
    Logging_Services('AppendLog', objVerifyRDSKeyLog, LogData, @RM, @FM)
    
    If ErrorMsg NE '' then Error_Services('Add', ErrorMsg)
    
end service


Service VerifyWOLogRDSKeyIndex(RDSNo)
    
    LogPath                = Environment_Services('GetApplicationRootPath') : '\LogFiles\RDS'
    LogDate                = Oconv(Date(), 'D4/')
    LogTime                = Oconv(Time(), 'MTS')
    LogFileName            = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' !RDS RDS{WO} Log.csv'
    Headers                = 'Logging DTM':@FM:'RDSNo':@FM:'WOStep':@FM:'Result'
    objVerifyRDSWoIndexLog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, COMMA$, Headers, '', False$, False$)
    
    LogData    = ''
    LogData<1> = OConv(Datetime(), 'DT/^S')
    LogData<2> = RDSNo
    LogData<4> = 'Begin ':Service
    Logging_Services('AppendLog', objVerifyRDSWoIndexLog, LogData, @RM, @FM)
    
    ErrorMsg = ''
    If RDSNo NE '' then
        If RowExists('RDS', RDSNo) then
            WONo = Xlate('RDS', RDSNo, 'WO', 'X')
            If WONo NE '' then
                WOLogRDSKeys = ''
                Extract_Si_Keys('RDS', 'WO', WONo, WOLogRDSKeys)
                LogData<3>   = WONo
                Logging_Services('AppendLog', objVerifyRDSWoIndexLog, LogData, @RM, @FM)
                Locate RDSNo in WOLogRDSKeys using @VM setting vPos then
                    LogData<1> = OConv(Datetime(), 'DT/^S')
                    LogData<4> = 'Index ok. RDSNo not missing from Btree index.'
                    Logging_Services('AppendLog', objVerifyRDSWoIndexLog, LogData, @RM, @FM)
                end else
                    LogData<1> = OConv(Datetime(), 'DT/^S')
                    LogData<4>          = 'RDSNo missing from Btree index. Generating index transaction.'
                    Logging_Services('AppendLog', objVerifyRDSWoIndexLog, LogData, @RM, @FM)     
                    // Add index transaction to update RDS_KEY relational index (target WO_STEP table)
                    IndexTransactionRow = 'WO':@FM:RDSNo:@FM:"":@FM:WONo:@FM
                    Open "!RDS" to BangTable then
                        Done = False$
                        For AttemptNo = 1 to NUM_ATTEMPTS$
                            If AttemptNo GT 1 then Delay(1)
                            Lock BangTable, 0 then
                                Read PendingTrans from BangTable, 0 else PendingTrans = '0':@FM
                                PendingTrans := IndexTransactionRow
                                Write PendingTrans on BangTable, 0 then
                                    LogData<1> = OConv(Datetime(), 'DT/^S')
                                    LogData<4> = 'Index transaction successfully added.'
                                    Logging_Services('AppendLog', objVerifyRDSWoIndexLog, LogData, @RM, @FM)
                                    Done       = True$
                                end else
                                    ErrorMsg = 'Error in ':Service:' service. Unable to write index transaction to !RDS. ':RDSNo
                                end
                                Unlock BangTable, 0 else ErrorMsg = 'Error in ':Service:' service. Unable to Open !RDS to add index transaction. ':RDSNo
                            end else
                                If AttemptNo GE NUM_ATTEMPTS$ then 
                                    ErrorMsg = 'Error in ':Service:' service. Unable to Lock !RDS to add index transaction. ':RDSNo
                                end
                            end
                        Until Done or ErrorMsg
                        Next AttemptNo
                    end else
                        ErrorMsg = 'Error in ':Service:' service. Unable to Open !RDS to add index transaction. ':RDSNo
                    end
                end
            end else
                LogData<1> = OConv(Datetime(), 'DT/^S')
                LogData<4> = 'WONo for RDS ':RDSNo:' is null. Nothing to update.'
                Logging_Services('AppendLog', objVerifyRDSWoIndexLog, LogData, @RM, @FM)                    
            end
        end
    end else
        ErrorMsg = 'Error in ':Service:' service. Null RDSNo passed in.'
    end
    
    If ErrorMsg NE '' then
        LogData<1> = OConv(Datetime(), 'DT/^S')
        LogData<4> = ErrorMsg
        Logging_Services('AppendLog', objVerifyRDSWoIndexLog, LogData, @RM, @FM)
    end
    
    LogData<1> = OConv(Datetime(), 'DT/^S')
    LogData<4> = 'End ':Service
    Logging_Services('AppendLog', objVerifyRDSWoIndexLog, LogData, @RM, @FM)
    
    If ErrorMsg NE '' then Error_Services('Add', ErrorMsg)
    
end service

Service CheckUserRDSModificationPerm(UserId)
    
    ErrorMsg = ''
    CanUserModify = False$
    
    If UserId NE '' then
        If RowExists('LSL_USERS', UserId) then
            Begin Case
                Case MemberOf(UserId, 'ENGINEERING')
                    CanUserModify = True$
                Case MemberOf(UserId, 'OI_ADMIN')
                    CanUserModify = True$
                Case MemberOf(UserId, 'SUPERVISOR')    
                    CanUserModify = True$
                Case MemberOf(UserId, 'LEAD')
                    CanUserModify = True$
                Case MemberOf(UserId, 'ENG_TECH')
                    CanUserModify = True$
                Case Otherwise$
                    CanUserModify = False$
            End Case
        end else
            ErrorMsg = 'User not found in Users table.'
        end
    end else
        ErrorMsg = 'User Id was null.'
    end
    
    If ErrorMsg NE '' then
        Error_Services('Add', ErrorMsg)
    end
    
    Response = CanUserModify
    
end service

Service UnsignPreEpiSignature(RDSNo, UserId)
    
    LogPath            = Environment_Services('GetApplicationRootPath') : '\LogFiles\RDS\Signatures'
    LogDate            = Oconv(Date(), 'D4/')
    LogTime            = Oconv(Time(), 'MTS')
    LogFileName        = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' Pre-Epi-Unsign.csv'
    Headers            = 'Logging DTM':@FM:'RDS No.':@FM:'User':@FM:'Message'
    objPreEpiUnsignLog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, COMMA$, Headers, '', False$, False$)
    LoggingDTM    = LogDate : ' ' : LogTime
    
    ErrorMsg = ''
    PreEpiUnsign = False$
    If RDSNo NE '' then
        If RowExists('RDS', RDSNo) then
            if UserId NE '' then
                if RowExists('LSL_USERS', UserId) then
                    CanUserModifyRDS = RDS_Services('CheckUserRDSModificationPerm', UserId)
                    If Error_Services('NoError') then
                        If CanUserModifyRDS then
                            UnsignPreEpiReady = QA_Services('UnsignPreEpiReady', RDSNo)
                            if Error_Services('NoError') then
                                If UnsignPreEpiReady then
                                    RDSRec = Database_Services('ReadDataRow', 'RDS', RDSNo, True$, 0, False$)
                                    if Error_Services('NoError') then
                                        RDSRec<RDS_PRE_EPI_SIG$> = ''
                                        RDSRec<RDS_PRE_EPI_SIG_DATE$> = ''
                                        RDSRec<RDS_PRE_EPI_SIG_TIME$> = ''
                                        RDSRec<RDS_VERIFY_QTY$> = ''
                                        RDSRec<RDS_REACTOR$> = ''
                                        Database_Services('WriteDataRow', 'RDS', RDSNo, RDSRec, True$, False$, False$)
                                        If Error_Services('NoError') then
                                            PreEpiUnsign = True$
                                        end else
                                            ErrorMsg = Error_Services('GetMessage')
                                        end
                                    end else
                                        ErrorMsg = Error_Services('GetMessage')
                                    end
                                end else
                                    ErrorMsg = 'Pre Epi is unable to be unsigned because a signature beyond pre-epi exists.'
                                end
                            end else
                                ErrorMsg = Error_Services('GetMessage')
                            end
                        end else
                            ErrorMsg = 'User does not have permissions to perform a Pre-Epi Unsign' 
                        end
                    end else
                        ErrorMsg = Error_Services('GetMessage')
                    end
                end else
                    ErrorMsg = 'User ' : UserId : ' not found in database.'
                end
            end else
                ErrorMsg = 'User Id was null.'
            end
        end else
            ErrorMsg = 'RDS ' : RDSNo : ' not found in database.'
        end
    end else
        ErrorMsg = 'RDS Number was null.'
    end
    
    If ErrorMsg EQ '' then
        LogData    = ''
		LogData<1> = LoggingDTM
		LogData<2> = RDSNo
		LogData<3> = UserId
		LogData<4> = 'Successfully unsigned pre-epi signature.'
		Logging_Services('AppendLog', objPreEpiUnsignLog, LogData, @RM, @FM, False$)
    end else
        LogData    = ''
		LogData<1> = LoggingDTM
		LogData<2> = RDSNo
		LogData<3> = UserId
		LogData<4> = ErrorMsg
		Logging_Services('AppendLog', objPreEpiUnsignLog, LogData, @RM, @FM, False$)
        Error_Services('Add', ErrorMsg) 
    end
    
    Response = PreEpiUnsign
    
end service


Service GetRdsZpl(RdsKey)
    
    ZPL        = ''
    ErrorMsg = ''
    If RdsKey NE '' then
        If RowExists('RDS', RdsKey) then
            Update_Index ("RDS", "WO", "")
            UniqueWOs           = ''
            CustNos             = ''
            CompositeKeys       = ''
            WONo                = XLATE('RDS',RdsKey,RDS_WO$,'X')
            WOStepKey           = Xlate('RDS', RdsKey, 'WO_STEP_KEY', 'X')
            CustNo              = XLATE('WO_LOG',WONo,WO_LOG_CUST_NO$,'X')
            WOLogRec            = XLATE('WO_LOG',WONo,'','X')
            CassNo              = XLATE('RDS',RdsKey,'CASS_NO','X')
            WOMatRec	        = XLATE('WO_MAT',WONo:'*':CassNo,'','X')
            ReShipCustNo        = WOMatRec<WO_MAT_RESHIP_CUST_NO$>
            ReShipDate          = WOMatRec<WO_MAT_RESHIP_DT$>
            ReShipCustEpiPartNo = WOMatRec<WO_MAT_RESHIP_CUST_EPI_PART_NO$>
            ReShipCustPO        = WOMatRec<WO_MAT_RESHIP_CUST_PO$>
            ReShipCustPartNo    = WOMatRec<WO_MAT_RESHIP_CUST_PART_NO$>
            
            If ReShipCustNo EQ '' then
                CustNo   = WOLogRec<WO_LOG_CUST_NO$>
                CustName = XLATE( 'RDS', RdsKey, 'ABBREV_OR_CO_NAME', 'X' )
                CustName = XLATE( 'COMPANY', CustNo, 'ABBREV_OR_CO_NAME', 'X' )
            end else
                CustNo      = ReShipCustNo
                CompRec		= XLATE('COMPANY',CustNo,'','X')
                CustName    = CompRec<COMPANY_ABBREV$>
            end
            
            If ReShipCustEpiPartNo EQ '' then
                EpiPartNo   = WOLogRec<WO_LOG_EPI_PART_NO$>
            end else
                EpiPartNo   = ReShipCustEpiPartNo
            end
            
            EpiPartRec			= XLATE('EPI_PART',EpiPartNo,'','X')
            CustEpiPartRec		= XLATE('CUST_EPI_PART',CustNo:'*':EpiPartNo,'','X')
            CustEpiPartSpecNo	= CustEpiPartRec<CUST_EPI_PART_SPEC_NO$>[-1,'B':@VM]	;* Last (current) Customer Spec Number
            
            ShipBagReq			= CustEpiPartRec<CUST_EPI_PART_SHIP_BAG_REQ$>
            
            If ReShipDate EQ '' then
                ReqShipDate		= OCONV( XLATE( 'WO_LOG', WO, WO_LOG_PROMISE_SHIP_DT$, 'X' ), 'D2/' )
            end else
                ReqShipDate     = ReShipDate
            end
            
            If ReShipCustPO EQ '' then
                IF CustNo = '7108' THEN
                    PO  = ''							;* Changed for "Foundry" customer additions 8/28/2012 JCH **********
                END ELSE
                    PO	= XLATE( 'RDS', RdsKey, rds_po$, 'X' )
                END
            end else
                PO  = ReShipCustPO
            end
            
            If ReShipCustPartNo EQ '' then
                PartNum	= XLATE( 'RDS', RdsKey, rds_part_num$, 'X' )
            end else
                PartNum = ReShipCustPartNo
            end
            
            ThickTarget			= OCONV( XLATE( 'RDS', RdsKey, 'THICK_TARGET_ALL', 'X' ), 'MD3' )
            ThickUnit			= XLATE( 'RDS', RdsKey, 'THICK_UNITS_ALL', 'X' )
            Dopant				= XLATE( 'RDS', RdsKey, 'DOPANT_L1', 'X' )
            ResTarget			= OCONV( XLATE( 'RDS', RdsKey, 'RES_TARGET_ALL', 'X' ), 'MD3' )
            ResUnit				= XLATE( 'RDS', RdsKey, 'RES_UNITS_ALL', 'X' )
            RecipeIDS			= XLATE( 'RDS', RdsKey, 'RECIPE_NO', 'X' )
            LotNum				= XLATE( 'RDS', RdsKey, rds_lot_num$, 'X' )
            RONum				= XLATE( 'RDS', RdsKey, 'RUN_ORDER_NUM', 'X' )
            PSNo				= XLATE( 'RDS', RdsKey, rds_prod_spec_id$, 'X' )
            SpecType			= OCONV( XLATE( 'RDS', RdsKey, 'SPEC_TYPE', 'X' ), '[SPEC_TYPE_CONV]' )
            SubPartNum			= XLATE( 'RDS', RdsKey, 'SUB_PART_NUM', 'X' )
            RecipeInfo			= XLATE( 'RECIPE', RecipeIDS, 'RECIPE_NAME_NO', 'X' )
            ThickVar			= XLATE( 'PROD_SPEC', PSNo, 'THICK_MEASURE_L1', 'X' )
            ResVar				= XLATE( 'PROD_SPEC', PSNo, 'RES_MEASURE_L1', 'X' )
            ConVar				= XLATE( 'PROD_SPEC', PSNo, 'CON_MEASURE_L1', 'X' )
            StressVar			= XLATE( 'PROD_SPEC', PSNo, 'STRESS_MEASURE', 'X' )
            TransVar			= XLATE( 'PROD_SPEC', PSNo, 'TRANS_MEASURE', 'X' )
            ReactTypes			= OCONV( XLATE( 'PROD_SPEC', PSNo, 'REACTOR_TYPE', 'X' ), '[REACT_TYPE_CONV,CODE]')
            SubOxide			= OCONV(EpiPartRec<EPI_PART_SUB_OXIDE$>,'B')
            
            SubPreCleans		= ''
            SubPostCleans		= ''
            PreAkrionRecipes	= ''
            PostAkrionRecipes	= ''
            
            SubPreCleans      = XLATE('PRS_STAGE',PSNo:'*PRE','TOOL_DISPLAY','X')
            SubPostCleans     = XLATE('PRS_STAGE',PSNo:'*POST','TOOL_DISPLAY','X')
            PreAkrionRecipes  = XLATE('PRS_STAGE',PSNo:'*PRE',PRS_STAGE_CLEAN_RECIPE$,'X')
            PostAkrionRecipes = XLATE('PRS_STAGE',PSNo:'*POST',PRS_STAGE_CLEAN_RECIPE$,'X')
            
            SWAP 'AKRION' WITH 'Akr' IN SubPreCleans
            SWAP 'AKRION' WITH 'Akr' IN SubPostCleans
            
            SWAP ' or ' WITH '/' IN SubPreCleans
            SWAP ' or ' WITH '/' IN SubPostCleans												;* To wide for field on small label "squeezed" data to fit JCH
            
            SubPartNum			= XLATE( 'RDS', RdsKey, 'SUB_PART_NUM', 'X' )
            WOStepKeys			= XLATE( 'RDS',RdsKey,RDS_WO_STEP_KEY$,'X' )							;* 05/21/2005 - JCH - Added for Order Entry/Vision upgrade
            
            IF CustNo = '7108' THEN
                CustPNDescs = ''																		;* 8/28/2012 - JCH Added for new "Foundry" customer ******************
            END ELSE
                CustPNDescs	= XLATE('EPI_PART',EpiPartNo,EPI_PART_EPI_PART_DESC$,'X')					;* 12/2/2011 - JCH Added for SAP interface
            END
            
            ************************************
            PreSurfscan			= XLATE( 'PRS_STAGE', PSNo:'*PRE' , PRS_STAGE_SURFSCAN_SIG_REQ$ , 'X' )
            FirstSurfscan		= XLATE( 'PRS_STAGE', PSNo:'*FWI' , PRS_STAGE_SURFSCAN_SIG_REQ$ , 'X' )
            PostCleanSurfscan	= XLATE( 'PRS_STAGE', PSNo:'*POST' , PRS_STAGE_SURFSCAN_SIG_REQ$ , 'X' )
            
            CleaningReqs		= ''
            
            SRPFlag				= XLATE( 'PROD_SPEC', PSNo, 'SRP_REQ', 'X' )			;* Updated in PROD_SPEC dictionary JCH 4/21/2015
            Qtys 				= XLATE( 'RDS', RdsKey, 'WAFERS_SCHEDULED', 'X' )
            MultilayVar			= XLATE( 'RDS', RdsKey, 'MULTI_LAYER', 'X' )
            
            * Lets break out thick and res and see if it gets measured based on frequency
            * Thickness
            
            ThickPrintVar   = ''
            ResPrintVar     = ''
            SRPPrintVar     = ''
            SRPTypePrintVar = ''  
            
            ThisThickVar  = ThickVar
            ThisResVar    = ResVar
            ThisConVar    = ConVar
            ThisStressVar = StressVar
            ThisTransVar  = TransVar
            ThisRunOrdNum = RONum
            ThisWO        = WONo
            ThisWOStepKey = WOStepKey						;* 5/21/2005 - JCH - Added for Order Entry/Vision upgrade
            MultiStep     = 0
            IF MultiStep > 0 THEN ThisWO = ThisWOStepKey[1,'*']			;* 5/21/2005 - JCH - Added for Order Entry/Vision upgrade	
            
            ThickTool   = field( ThisThickVar, '~', 1 )
            ThickType   = field( ThisThickVar, '~', 2 )
            ThickRecipe = field( ThisThickVar, '~', 3 )
            ThickFreq   = field( ThisThickVar, '~', 4 )
            ThickFirst  = field( ThisThickVar, '~', 6 )
            ThickLast   = field( ThisThickVar, '~', 7 )
            
            ResTool   = field( ThisResVar, '~', 1 )
            ResType   = field( ThisResVar, '~', 2 )
            ResRecipe = field( ThisResVar, '~', 3 )
            ResFreq   = field( ThisResVar, '~', 4 )
            ResFirst  = field( ThisResVar, '~', 6 )
            ResLast   = field( ThisResVar, '~', 7 )
            
            ConTool   = field( ThisConVar, '~', 1 )
            ConType   = field( ThisConVar, '~', 2 )
            ConRecipe = field( ThisConVar, '~', 3 )
            ConFreq   = field( ThisConVar, '~', 4 )
            ConFirst  = field( ThisConVar, '~', 6 )
            ConLast   = field( ThisConVar, '~', 7 )
            
            StressTool   = field( ThisStressVar, '~', 1 )
            StressType   = field( ThisStressVar, '~', 2 )
            StressRecipe = field( ThisStressVar, '~', 3 )
            StressFreq   = field( ThisStressVar, '~', 4 )
            StressFirst  = field( ThisStressVar, '~', 6 )
            StressLast   = field( ThisStressVar, '~', 7 )
            
            TransTool   = field( ThisTransVar, '~', 1 )
            TransType   = field( ThisTransVar, '~', 2 )
            TransRecipe = field( ThisTransVar, '~', 3 )
            TransFreq   = field( ThisTransVar, '~', 4 )
            TransFirst  = field( ThisTransVar, '~', 6 )
            TransLast   = field( ThisTransVar, '~', 7 )
            
            IF MultiStep > 0 THEN
                WOBoxCnt = COUNT(XLATE('WO_STEP',ThisWOStepKey,6,'X'),@VM)		;* 5/21/2005 - JCH - Added for Order Entry/Vision upgrade
            END ELSE
                
                EXTRACT_SI_KEYS( 'RDS', 'WO', ThisWO, RDSsInWO )				;* Original Code
                
                WOBoxCnt = FIELDCOUNT( RDSsInWO, @VM )							;* Original Code
            END
            
            * First check thickness
            
            PrintThickness = false$
            IF ( ThisRunOrdNum = 1 ) AND ( ThickFirst )			THEN PrintThickness = true$
            IF ( ThisRunOrdNum = WOBoxCnt ) AND ( ThickLast )	THEN PrintThickness = true$
            
            IF ThickFreq THEN
                * Need to check run order and see if it needs to be printed
                IF ThickFreq = 1 THEN
                    PrintThickness = true$
                END ELSE
                    * Generate @FM delim of runs that should print
                    RoPrintFlag = str( @FM, WOBoxCnt-1 )
                    FOR J  = 1 TO WOBoxCnt step ThickFreq
                        RoPrintFlag<J> = 1
                    NEXT J 
                    IF ( RoPrintFlag<ThisRunOrdNum> = 1 )		THEN PrintThickness = true$            
                END 
            END
            
            IF PrintThickness THEN
                PSNRec    = XLATE( 'PROD_SPEC', PSNo, '', 'X' )
                SpecSub   = PSNRec<prod_spec_spec_substrate$>
                
                TOrient = SpecSub<1,QSSubOrientation$>
                convert '<' TO '' in TOrient
                convert '>' TO '' in TOrient
                Orient = TOrient
                
                ThickPrintVar = ThickTool:'~':ThickType:'~':Orient:'~':ThickRecipe
            END ELSE
                ThickPrintVar = ''
            END
            
            * Now check resistivity
            
            PrintResistivity = false$
            IF ( ThisRunOrdNum = 1 ) and ( ResFirst )			THEN PrintResistivity = true$
            IF ( ThisRunOrdNum = WOBoxCnt ) and ( ResLast )		THEN PrintResistivity = true$
            
            IF ResFreq THEN
                * NEED TO CHECK RUN ORDER AND SEE IF IT NEEDS TO BE PRINTED
                IF ResFreq = 1 THEN
                    PrintResistivity = true$
                END ELSE
                    * Generate @FM delim of runs that should print
                    RoPrintFlag = str( @FM, WOBoxCnt-1 )
                    FOR J  = 1 TO WOBoxCnt step ResFreq
                        RoPrintFlag<J> = 1
                    NEXT J 
                    IF ( RoPrintFlag<ThisRunOrdNum> = 1 )		THEN PrintResistivity = true$
                END 
            END
            
            IF PrintResistivity THEN
                PSNRec    = XLATE( 'PROD_SPEC', PSNo, '', 'X' )
                SpecSub   = PSNRec<prod_spec_spec_substrate$>				;******* Moved to EPI_PART
                
                TOrient = SpecSub<1,QSSubOrientation$>
                convert '<' TO '' in TOrient
                convert '>' TO '' in TOrient
                Orient = TOrient
                
                ResPrintVar = ResTool:'~':ResType:'~':Orient:'~':ResRecipe
            END ELSE
                ResPrintVar = ''
            END
            
            PrintSRP = false$
            SRPType  = ''
            IF SRPFlag THEN
                * We know that SRP is one of the tools
                
                IF PrintThickness THEN
                    IF ThickTool = 'SRP' THEN
                        PrintSRP = true$
                        SRPType  = ThickType
                    END
                END
                
                IF PrintResistivity THEN
                    IF ResTool = 'SRP' THEN
                        PrintSRP = true$
                        SRPType  = ResType
                    END
                END
                
                
                IF NOT(PrintSRP) THEN
                    
                    * Now need to check concentration
                    
                    IF ConTool = 'SRP' THEN
                        IF ( ThisRunOrdNum = 1 ) and ( ConFirst )		THEN PrintSRP = true$
                        IF ( ThisRunOrdNum = WOBoxCnt ) and ( ConLast )	THEN PrintSRP = true$
                        
                        IF ConFreq THEN
                            * Need to check run order and see if it needs to be printed
                            IF ConFreq = 1 THEN
                                PrintSRP = true$
                            END ELSE
                                * Generate fm delim of runs that should print
                                RoPrintFlag = str( @FM, WOBoxCnt-1 )
                                FOR J  = 1 TO WOBoxCnt step ConFreq
                                    RoPrintFlag<J> = 1
                                NEXT J 
                                IF ( RoPrintFlag<ThisRunOrdNum> = 1 )	THEN PrintSRP = true$
                            END 
                        END
                    END
                    IF PrintSRP THEN SRPType = ConType   
                END
                
                
                IF NOT(PrintSRP) THEN
                    
                    * Now need to check stress
                    
                    IF StressTool = 'SRP' THEN
                        IF ( ThisRunOrdNum = 1 ) AND ( StressFirst ) 		THEN PrintSRP = true$
                        IF ( ThisRunOrdNum = WOBoxCnt ) AND ( StressLast )	THEN PrintSRP = true$
                        
                        IF StressFreq THEN
                            * Need to check run order and see if it needs to be printed
                            IF StressFreq = 1 THEN
                                PrintSRP = true$
                            END ELSE
                                * Generate @FM delim of runs that should print
                                RoPrintFlag = str( @FM, WOBoxCnt-1 )
                                FOR J  = 1 TO WOBoxCnt step StressFreq
                                    RoPrintFlag<J> = 1
                                NEXT J 
                                IF ( RoPrintFlag<ThisRunOrdNum> = 1 )		THEN PrintSRP = true$
                            END
                        END
                    END
                    IF PrintSRP THEN  SRPType = StressType
                END
                
                IF NOT(PrintSRP) THEN
                    * Now need to check transition
                    IF TransTool = 'SRP' THEN
                        IF ( ThisRunOrdNum = 1 ) AND ( TransFirst )			THEN PrintSRP = true$
                        IF ( ThisRunOrdNum = WOBoxCnt ) AND ( TransLast )	THEN PrintSRP = true$
                        
                        IF TransFreq THEN
                            * Need to check run order and see if it needs to be printed
                            IF TransFreq = 1 THEN
                                PrintSRP = true$
                            END ELSE
                                * Generate fm delim of runs that should print
                                RoPrintFlag = str( @FM, WOBoxCnt-1 )
                                FOR J  = 1 TO WOBoxCnt step TransFreq
                                    RoPrintFlag<J> = 1
                                NEXT J 
                                IF ( ROPrintFlag<ThisRunOrdNum> = 1 )		THEN PrintSRP = true$
                            END
                            IF PrintSRP THEN SRPType = TransType
                        END
                    END
                END
                
                IF PrintSRP THEN
                    SRPPrintVar     = true$
                    SRPTypePrintVar = SRPType
                END ELSE
                    SRPPrintVar     = false$
                    SRPTypePrintVar = ''
                END
                
            END ELSE
                SRPPrintVar     = false$
                SRPTypePrintVar = ''
            END
            
            * Got all data now we can print
            
            PrintCustName = CustName
            
            IF MultiStep > 0 THEN
                WONo = WONo:'.':MultiStep						;* 5/21/2005 - JCH - Added for Order Entry/Vision upgrade
                WONo = WOStepKeys
                CONVERT '*' TO '.' IN WONo
            END
            
            PrintWO              = WoNo		 		;* 5/21/2005 - JCH - Changed for Order Entry/Vision upgrade
            PrintPSNNo           = PSNo		;* 6/10/2005 - JCH - Added per manufacturing manager request 
            PrintLotNum          = LotNum
            PrintRecipeInfo      = RecipeInfo
            PrintSpecType        = SpecType           
            PrintRONum           = RONum
            PrintRdsId           = RdsKey
            PrintThickPrintVar   = ThickPrintVar
            PrintResPrintVar     = ResPrintVar
            PrintMultiLayVar     = MultiLayVar
            PrintSRPPrintVar     = SRPPrintVar
            PrintSRPTypePrintVar = SRPTypePrintVar
            PrintQty             = Qtys
            PrintThickTargets    = ''
            PrintThickTargets    = ThickTarget:ThickUnit
            
            PrintResTargets = ''
            IF ResTarget[1,1] = '.' THEN
                TargetVal = '0':ResTarget
            END ELSE
                TargetVal = ResTarget
            END
            PrintResTargets = TargetVal:ResUnit
            
            PrintPO					= PO
            PrintPartNum			= PartNum:' ':CustPNDescs
            PrintPartNo				= PartNum
            PrintSubPartNum			= SubPartNum
            
            SubSupplier	= obj_Vendor_Code('SemiToEpi',WOMatRec<WO_MAT_SUB_VEND_CD$>)
            
            VendCd = XLATE('COMPANY',CustNo,COMPANY_VEND_CD$,'X')
            PrintCompany = XLATE('COMPANY', CustNo, COMPANY_CO_NAME$, 'X')
            
            IF VendCd NE '' THEN
                SubSupplier = VendCd	;* Substrate supplier code found in the EPI_PART window. 12/9/2011 for cust 7067
            END
            
            PrintSuppCd				= SubSupplier
            PrintDopant				= Dopant
            PrintShipBagReq			= ShipBagReq			;* Changed to single value ShipBagReq  9/27/2012 JCH
            PrintReqShipDate		= ReqShipDate
            PrintSubOxide			= SubOxide
            PrintReactType			= ReactTypes
            PrintSubPreClean		= SubPreCleans
            PrintSubPostClean		= SubPostCleans
            PrintPreAkrionRecipe	= PreAkrionRecipes
            PrintPostAkrionRecipe	= PostAkrionRecipes
            
            APreRec  = ''
            APostRec = ''
            IF ( PreAkrionRecipes <> '' ) THEN
                APreRec  = ' ':PreAkrionRecipes<I>:' '
                SubOxide = 'No' ;* IF AKRION THEN NO OXIDE STRIP
            END
            
            IF ( PostAkrionRecipes <> '' ) THEN
                APostRec = ' ':PostAkrionRecipes
            END
            
            swap '~' with fmt( ' ', 'L#2' ) in PrintThickPrintVar
            swap UNIT_MICROMETER$ with 'um' in PrintThickTargets
            swap UNIT_OHM_CM$     with 'ohm.cm' in PrintThickTargets
            swap UNIT_OHM_PER_SQ$ with 'ohm/sq' in PrintThickTargets
            swap UNIT_A$ with 'A' in PrintThickTargets  
            
            swap '~' with fmt( ' ', 'L#2' ) in PrintResPrintVar
            swap UNIT_MICROMETER$ with 'um' in PrintResTargets
            swap UNIT_OHM_CM$     with 'ohm.cm' in PrintResTargets
            swap UNIT_OHM_PER_SQ$ with 'ohm/sq' in PrintResTargets
            swap UNIT_A$ with 'A' in PrintResTargets
            
            //only print cheat sheet label on cassette #1
            IF CassNo EQ 1 then

                PsnRec = XLATE('PROD_SPEC', PSNo, '', 'X')
                
                SpecEpi		= PsnRec<prod_spec_spec_epi$>
                SpecSub		= PsnRec<PROD_SPEC_SPEC_SUBSTRATE$>
                
                ThkSmile	= PsnRec<PROD_SPEC_THICK_SMILE_REQ$>
                ThkShift	= Field(PsnRec, @FM, PROD_SPEC_THICK_LIMIT_SHIFT$)
                
                ResShift	= Field(PsnRec, @FM, PROD_SPEC_RES_LIMIT_SHIFT$)
                
                SSRecipe	= XLATE('PRS_STAGE', PSNo:'*LWI', 5, 'X')
                PCRecipe	= XLATE('PRS_STAGE', PSNo, 'POST_CLEAN_SURFSCAN_RECIPE', 'X')
                CustPart	= PsnRec<PROD_SPEC_CUST_PART_NO$>
                WaferSize	= XLATE('EPI_PART', CustPart, EPI_PART_SUB_WAFER_SIZE$, 'X')
                *Field(SpecSub, @VM, QSSubWafersize$)

                convert char(248) to @fm in SpecEpi ;* char(248) is used to separate layers
                layerCount = fieldcount( SpecEpi, @fm )
                layerInfo =  SpecEpi<layerCount>
                //if this is just 1 layer, we're returning that info, if 3 layers, we're returning layer 3 (combo)
                
                ThkData		= Field(layerInfo, @VM, QSEpiThickMeasure$)
                ResData		= Field(layerInfo, @VM, QSEpiResMeasure$)
                ConData		= Field(layerInfo, @VM, QSEpiConcMeasure$)
                CResData	= Field(layerInfo, @VM, QSEpiCresMeasure$)
                
                ThkMin		= OCONV(Field(layerInfo, @VM, QSEpiMinThick$), 'MD3')
                ThkMax		= OCONV(Field(layerInfo, @VM, QSEpiMaxThick$), 'MD3')
                ThkTarg		= ''
                If ThkMin NE '' Then ThkTarg = oConv(iconv((ThkMin+ThkMax)/2, 'MD3'), 'MD3')

                ResMin		= OCONV(Field(layerInfo, @VM, QSEpiResMinThick$), 'MD3')
                ResMax		= OCONV(Field(layerInfo, @VM, QSEpiResMaxThick$), 'MD3')
                ResTarg		= ''
                If ResMin NE '' Then ResTarg = oConv(iconv((ResMin+ResMax)/2, 'MD3'), 'MD3')
                
                ConMin		= OCONV(Field(layerInfo, @VM, QSEpiConcMinThick$), 'MD3')
                ConMax		= OCONV(Field(layerInfo, @VM, QSEpiConcMaxThick$), 'MD3')
                ConTarg		= ''
                If ConMin NE '' Then ConTarg = oConv(iconv((ConMin+ConMax)/2, 'MD3'), 'MD3')

                CResMin		= OCONV(Field(layerInfo, @VM, QSEpiCresMin$), 'MD3')
                CResMax		= OCONV(Field(layerInfo, @VM, QSEpiCresMax$), 'MD3')
                CResTarg	= ''
                If CResMin NE '' Then CResTarg = oConv(iconv((CResMin+CResMax)/2, 'MD3'), 'MD3')

                ThkTool		= Field(ThkData, '~', PS$TOOL)
                ThkType		= Field(ThkData, '~', PS$TYPE)
                ThkRecipe	= Field(ThkData, '~', PS$RECIPE)
                ThkFreq		= Field(ThkData, '~', PS$FREQ)
                ThkPattern	= Field(ThkData, '~', PS$PATTERN)
                
                ResTool		= Field(ResData, '~', PS$TOOL)
                ResType		= Field(ResData, '~', PS$TYPE)
                ResRecipe	= Field(ResData, '~', PS$RECIPE)
                ResFreq		= Field(ResData, '~', PS$FREQ)
                ResPattern	= Field(ResData, '~', PS$PATTERN)
                
                ConTool		= Field(ConData, '~', PS$TOOL)
                ConType		= Field(ConData, '~', PS$TYPE)
                ConRecipe	= Field(ConData, '~', PS$RECIPE)
                ConFreq		= Field(ConData, '~', PS$FREQ)
                ConPattern	= Field(ConData, '~', PS$PATTERN)
                    
                CResTool	= Field(CResData, '~', PS$TOOL)
                CResType	= Field(CResData, '~', PS$TYPE)
                CResRecipe	= Field(CResData, '~', PS$RECIPE)
                CResFreq	= Field(CResData, '~', PS$FREQ)
                CResPattern	= Field(CResData, '~', PS$PATTERN)

                IF ThkMin		EQ '' THEN ThkMin		= '--'
                IF ThkMax		EQ '' THEN ThkMax		= '--'
                IF ThkTarg		EQ '' THEN ThkTarg		= '--'
                IF ThkTool		EQ '' THEN ThkTool		= '--'
                IF ThkType		EQ '' THEN ThkType		= '--'
                IF ThkRecipe	EQ '' THEN ThkRecipe	= '--'
                IF ThkPattern	EQ '' THEN ThkPattern	= '--'
                IF ThkFreq		EQ '' THEN ThkFreq		= '--'

                IF ResMin		EQ '' THEN ResMin		= '--'
                IF ResMax		EQ '' THEN ResMax		= '--'
                IF ResTarg		EQ '' THEN ResTarg		= '--'
                IF ResTool		EQ '' THEN ResTool		= '--'
                IF ResType		EQ '' THEN ResType		= '--'
                IF ResRecipe	EQ '' THEN ResRecipe	= '--'
                IF ResPattern	EQ '' THEN ResPattern	= '--'
                IF ResFreq		EQ '' THEN ResFreq		= '--'

                IF ConMin		EQ '' THEN ConMin		= '--'
                IF ConMax		EQ '' THEN ConMax		= '--'
                IF ConTarg		EQ '' THEN ConTarg		= '--'
                IF ConTool		EQ '' THEN ConTool		= '--'
                IF ConType		EQ '' THEN ConType		= '--'
                IF ConRecipe	EQ '' THEN ConRecipe	= '--'
                IF ConPattern	EQ '' THEN ConPattern	= '--'
                IF ConFreq		EQ '' THEN ConFreq		= '--'

                IF CResMin		EQ '' THEN CResMin		= '--'
                IF CResMax		EQ '' THEN CResMax		= '--'
                IF CResTarg		EQ '' THEN CResTarg		= '--'
                IF CResTool		EQ '' THEN CResTool		= '--'
                IF CResType		EQ '' THEN CResType		= '--'
                IF CResRecipe	EQ '' THEN CResRecipe	= '--'
                IF CResPattern	EQ '' THEN CResPattern	= '--'
                IF CResFreq		EQ '' THEN CResFreq		= '--'
                
                Swap @VM with ' / ' in SSRecipe
                IF PCRecipe EQ '' Then PCRecipe = '--'
                
                If ThkSmile EQ '' OR ThkSmile EQ '0' then
                    ThkSmile = 'N'
                end else
                    ThkSmile = 'Y'
                end
                
                If ThkShift EQ '' OR ThkShift EQ '0' then
                    ThkShift = 'N'
                end else
                    ThkShift = 'Y'
                end
                
                If ResShift EQ '' OR ResShift EQ '0' then
                    ResShift = 'N'
                end else
                    ResShift = 'Y'
                end

                if Len(ThkMin) > 6 then
                    ThkMin = ThkMin[1,6]
                end
                if Len(ThkMax) > 6 then
                    ThkMax = ThkMax[1,6]
                end	
                if Len(ThkTarg) > 6 then
                    ThkTarg = ThkTarg[1,6]
                end
                
                if Len(ResMin) > 6 then
                    ResMin = ResMin[1,6]
                end
                if Len(ResMax) > 6 then
                    ResMax = ResMax[1,6]
                end	
                if Len(ResTarg) > 6 then
                    ResTarg = ResTarg[1,6]
                end
                
                if Len(ConMin) > 6 then
                    ConMin = ConMin[1,6]
                end
                if Len(ConMax) > 6 then
                    ConMax = ConMax[1,6]
                end	
                if Len(ConTarg) > 6 then
                    ConTarg = ConTarg[1,6]
                end	
                
                
                if Len(CResMin) > 6 then
                    CResMin = CResMin[1,6]
                end
                if Len(CResMax) > 6 then
                    CResMax = CResMax[1,6]
                end	
                if Len(CResTarg) > 6 then
                    CResTarg = CResTarg[1,6]
                end	
                
                ****** Begin ZPL
                ZPL := '^XA'
                ZPL := '^LH10,0'
                ZPL := '^PR2'		;* Print speed 2 inches per second
                ZPL := '^LL406'		;* Label length @203 dpi
                ZPL := '^PW900'
                ZPL := '^MD0'		;* Media darkness							
                ZPL := '^MMT'		;* Media mode t=tear off mode
                
                ****** 1st/2nd Line 
                ZPL := '^FO30,20^A0,,40^FDWO ':PrintWO:'^FS'
                ZPL := '^FO590,10^A050,30^FDPSN ':PrintPSNNo:'^FS'
                ZPL := '^FO555,40^A050,30^FDDopant ':PrintDopant:'^FS'	
                
                ****** 3rd Line - headers
                ZPL := '^FO85,100^A050,23^FDMin^FS'
                ZPL := '^FO145,100^A050,23^FDTarg^FS'
                ZPL := '^FO210,100^A050,23^FDMax^FS'
                ZPL := '^FO280,100^A050,23^FDTool^FS'
                ZPL := '^FO330,100^A050,23^FDType^FS'
                ZPL := '^FO470,100^A050,23^FDRecipe^FS'
                ZPL := '^FO600,100^A050,23^FDPattern^FS'
                ZPL := '^FO740,100^A050,23^FDFreq^FS'
                
                ****** 4th Line - Thickness (Thk)
                ZPL := '^FO70,130^GB720,0,3^FS'
                ZPL := '^FO70,130^GB0,160,3^FS'
                ZPL := '^FO10,140^A050,23^FDThk:^FS'
                ZPL := '^FO80,140^A050,23^FD':ThkMin:'^FS'
                ZPL := '^FO145,140^A050,23^FD':ThkTarg:'^FS'
                ZPL := '^FO210,140^A050,23^FD':ThkMax:'^FS'
                ZPL := '^FO280,140^A050,23^FD':ThkTool[1,5]:'^FS'
                ZPL := '^FO330,140^A050,23^FD':ThkType[1,11]:'^FS'
                ZPL := '^FO470,140^A050,23^FD':ThkRecipe[1,11]:'^FS'
                ZPL := '^FO600,140^A050,23^FD':ThkPattern[1,11]:'^FS'
                ZPL := '^FO750,140^A050,23^FD':ThkFreq:'^FS'

                ****** 5th Line - Resistivity (Res)
                ZPL := '^FO10,170^GB780,0,3^FS'
                ZPL := '^FO10,180^A050,23^FDRes:^FS'
                ZPL := '^FO80,180^A050,23^FD':ResMin:'^FS'
                ZPL := '^FO145,180^A050,23^FD':ResTarg:'^FS'
                ZPL := '^FO210,180^A050,23^FD':ResMax:'^FS'
                ZPL := '^FO280,180^A050,23^FD':ResTool[1,5]:'^FS'
                ZPL := '^FO330,180^A050,23^FD':ResType[1,11]:'^FS'
                ZPL := '^FO470,180^A050,23^FD':ResRecipe[1,11]:'^FS'
                ZPL := '^FO600,180^A050,23^FD':ResPattern[1,11]:'^FS'
                ZPL := '^FO750,180^A050,23^FD':ResFreq:'^FS'

                ****** 6th Line - Contact (Con)
                ZPL := '^FX***LINE 4'
                ZPL := '^FO10,210^GB780,0,3^FS'
                ZPL := '^FO10,220^A050,23^FDCon:^FS'
                ZPL := '^FO80,220^A050,23^FD':ConMin:'^FS'
                ZPL := '^FO145,220^A050,23^FD':ConTarg:'^FS'
                ZPL := '^FO210,220^A050,23^FD':ConMax:'^FS'
                ZPL := '^FO280,220^A050,23^FD':ConTool[1,5]:'^FS'
                ZPL := '^FO330,220^A050,23^FD':ConType[1,11]:'^FS'
                ZPL := '^FO470,220^A050,23^FD':ConRecipe[1,11]:'^FS'
                ZPL := '^FO600,220^A050,23^FD':ConPattern[1,11]:'^FS'
                ZPL := '^FO750,220^A050,23^FD':ConFreq:'^FS'
                
                ****** 7th Line - Contact Resistance (CRes)
                ZPL := '^FO10,250^GB780,0,3^FS'
                ZPL := '^FO10,260^A050,23^FDCRes:^FS'
                ZPL := '^FO80,260^A050,23^FD':CResMin:'^FS'
                ZPL := '^FO145,260^A050,23^FD':CResTarg:'^FS'
                ZPL := '^FO210,260^A050,23^FD':CResMax:'^FS'
                ZPL := '^FO280,260^A050,23^FD':CResTool[1,5]:'^FS'
                ZPL := '^FO330,260^A050,23^FD':CResType[1,11]:'^FS'
                ZPL := '^FO470,260^A050,23^FD':CResRecipe[1,11]:'^FS'
                ZPL := '^FO600,260^A050,23^FD':CResPattern[1,11]:'^FS'
                ZPL := '^FO750,260^A050,23^FD':CResFreq:'^FS'
                
                ZPL := '^FO10,290^GB780,0,3^FS'
                
                ****** 8th Line - Special Instructions
                ZPL := '^FO10,310^A050,23^FDSurfScan: ':SSRecipe:'^FS'
                ZPL := '^FO10,340^A050,23^FDPostClean: ':PCRecipe:'^FS'
                ZPL := '^FO10,370^A050,23^FDWafer Size: ':WaferSize:'^FS'
                ZPL := '^FO560,310^A050,23^FDSmile Required: ':ThkSmile:'^FS'
                ZPL := '^FO560,340^A050,23^FDSPC Thk Shift: ':ThkShift:'^FS'
                ZPL := '^FO560,370^A050,23^FDSPC Res Shift: ':ResShift:'^FS'

                ZPL:= '^XZ':CRLF$
                ****** End ZPL

            end
            
            * Standard RDS Label print method
            IF PrintSpecType	EQ 'Production'		THEN PrintSpecType	= 'Prod'
            IF PrintSpecType	EQ 'Pre-Production' THEN PrintSpecType	= 'Pre'

            EpiPartRec			= XLATE('EPI_PART',EpiPartNo,'','X')
            IsExportControl		= EpiPartRec<EPI_PART_EXPORT_CONTROL$>
            CustEpiPartRec		= XLATE('CUST_EPI_PART',CustNo:'*':EpiPartNo,'','X')
            CustEpiPartSpecNo	= CustEpiPartRec<CUST_EPI_PART_SPEC_NO$>[-1,'B':@VM]	;* Last (current) Customer Spec Number
            PrintPSN			= XLATE('RDS',PrintRDSId,'PROD_SPEC_ID', 'X')
            PrintRecipe			= ''
            PSStageKey			= PrintPSN:'*QA'
            PrintRecipe			= XLATE('PRS_STAGE',PSStageKey,'MET_RECIPE','X')
            PrintQARecipe		= FIELD(PrintRecipe, @VM, 1)
            PrintCompany		= PrintCompany[1,32]

            // Looping instead of using ^PQ2 since I have to set sequential key in 2D barcode
            For cnt = 1 to 2
                ZPL := '^XA'
                ZPL := '^LH0,0'
                ZPL := '^PR1'		;* Print speed 2 inches per second
                ZPL := '^LL406'		;* Label length @203 dpi
                ZPL := '^PW900'
                ZPL := '^MD5'		;* Media darkness							
                ZPL := '^MMT'		;* Media mode t=tear off mode
                
                ****** First Line, Cust, WO and RO
                ZPL := '^FO25,25^AC,18^FDCust: ^FS':CRLF$
                ZPL := '^FO95,14^A045,35^FD':PrintCompany:'^FS':CRLF$
                ZPL	:= '^FO570,25^AC18^FDWO: ^FS':CRLF$
                ZPL := '^FO610,14^A045,35^FD':PrintWO:'^FS':CRLF$
                ZPL	:= '^FO730,25^AC18^FDRO: ^FS':CRLF$
                ZPL	:= '^FO770,14^A045,35^FD':PrintRONum:'^FS':CRLF$
                
                ****** Second Line, Cust Spec, PSN and Type
                ZPL	:= '^FO25,75^AC18^FD(P)Cust Spec: ^FS':CRLF$
                ZPL	:= '^FO185,65^A045,35^FD':CustEpiPartSpecNo:'^FS':CRLF$
                ZPL	:= '^FO25,95^BY2,2.0^B3,,50,N^FDP':CustEpiPartSpecNo:'^FS':CRLF$
                ZPL	:= '^FO640,75^AC18^FDPSN:^FS':CRLF$
                ZPL	:= '^FO690,65^A045,35^FD':PrintPSN:'^FS':CRLF$
                ZPL	:= '^FO630,125^AC18^FDType:^FS':CRLF$
                ZPL	:= '^FO690,115^A045,30^FD':PrintSpecType:'^FS':CRLF$
                
                ****** Third Line, Sub Lot, Qty and RDS
                ZPL	:= '^FO25,175^AC18^FD(2T)Sub Lot:^FS':CRLF$
                ZPL	:= '^FO175,165^A045,35^FD':PrintLotNum:'^FS':CRLF$
                ZPL	:= '^FO25,195^BY1,3.0^B3,,50,N^FD2T':PrintLotNum:'^FS':CRLF$
                ZPL	:= '^FO400,175^AC18^FD(Q)Qty:^FS':CRLF$
                ZPL	:= '^FO500,165^A045,35^FD':PrintQty:'^FS':CRLF$
                ZPL	:= '^FO400,195^B3,,50,N^FDQ':PrintQty:'^FS':CRLF$
                ZPL	:= '^FO590,175^AC18^FD(1T)RDS: ^FS':CRLF$
                ZPL	:= '^FO690,165^A045,35^FD':PrintRDSId:'^FS':CRLF$
                ZPL	:= '^FO630,195^B3,,50,N^FD1T':PrintRDSId:'^FS':CRLF$
                
                ****** Fourth Line, SubPN, Vendor and QA Metrology recipe
                ZPL	:= '^FO25,275^AC18^FD(S)Sub PN:^FS':CRLF$
                ZPL	:= '^FO145,265^A045,35^FD':PrintSubPartNum:'^FS':CRLF$
                ZPL	:= '^FO25,295^B3,,50,N^FDS':PrintSubPartNum:'^FS':CRLF$
                ZPL	:= '^FO400,275^AC18^FD(1V)Vend:^FS':CRLF$
                ZPL	:= '^FO510,265^A045,35^FD':PrintSuppCd:'^FS':CRLF$
                ZPL	:= '^FO400,295^B3,,50,N^FD1V':PrintSuppCd:'^FS':CRLF$
                ZPL	:= '^FO590,275^AC18^FD':PrintQARecipe:'^FS':CRLF$
                
                ****** Fifth Line, Motto and Data Matrix barcode
                ZPL	:= '^FO25,370^A045,28':CRLF$
                ZPL	:= "^FDWe do what we promise. That's quality made by Infineon.^FS":CRLF$
                ZPL	:= '^FO725,320^CI28':CRLF$
                ZPL	:= '^BXN,2,200^FDP':CustEpiPartSpecNo:'|S':PrintSubPartNum:'|1T':PrintRDSId:'|2T':PrintLotNum:'|':PrintPSN:'|Q':PrintQty:'|1V':PrintSuppCd:'|SEQ':cnt:'^FS':CRLF$
                
                ****** End the print job (ZPL script)
                ZPL:= '^XZ'
            Next
            
        end else
            ErrorMsg = 'Error in ':Service:' service. RDS ':RdsKey:' does not exist.'
        end
    end else
        ErrorMsg = 'Error in ':Service:' service. Null RdsKey passed in.'
    end
    
    If ErrorMsg EQ '' then
        Response = ZPL
    end else
        Error_Services('Add', ErrorMsg)
    end
    
end service


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

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

