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

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

    Name        :   Material_Services

    Description :   Handler program for all module related services.

    Notes       :   Service module to support environmental state issues. Environmental refers to the state of the
                    operating system, which includes version, client vs. server, and path to critical systems.

    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)
        04/16/18    dmb     Original programmer.
        04/24/18    dmb     Modify GetAvailableMakeupWafers to avoid returning cassette with 0 wafers.
        10/25/18    dmb     Modify GetAvailableMakeupWafers to better support EpiPro.
        09/07/23	djm		Add GetLotHistory Service.

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

#pragma precomp SRP_PreCompiler

$insert LOGICAL
$insert SERVICE_SETUP
$insert WO_LOG_EQUATES
$insert WO_MAT_EQUATES
$insert WO_STEP_EQUATES
$insert PROD_SPEC_EQUATES
$insert RDS_EQUATES
$insert RLIST_EQUATES
$insert WO_MAT_QUEUE_EQUATES
$insert WM_OUT_QUEUE_EQUATES
$insert WO_STEP_QUEUE_EQUATES
$insert WM_OUT_EQUATES
$insert RDS_QUEUE_EQUATES
$insert REACT_RUN_EQUATES
$insert NOTIFICATION_EQUATES
$insert PROD_VER_EQUATES
$insert NCR_EQUATES
$insert CLEAN_INSP_EQUATES
$insert RDS_LAYER_EQUATES
$insert RDS_TEST_EQUATES
$insert TW_USE_EQUATES
$insert MAKEUP_WAFERS_EQUATES

Equ Tab$   to Char(9)
Equ Esc$   to \1B\
Equ CRLF$  to \0D0A\
Equ COMMA$ to ','

Common /EnvironmentServices/ Unused1@, Unused2@, Unused3@, Unused4@, Unused5@, Unused6@, Unused7@, Unused8@, Unused9@, Unused10@

Declare function    Material_Services, Memory_Services, Database_Services, SRP_Array, obj_WO_Mat, obj_WM_Out
Declare function    Logging_Services, Environment_Services, Datetime, obj_WO_Step, Database_Services, SQL_Services
Declare function	SRP_Rotate_Array, SRP_DateTime, obj_WO_Log, obj_Shipment, SRP_Date, Rds_Services
Declare subroutine  Material_Services, Memory_Services, Database_Services, SRP_Array, Btree.Extract, Logging_Services
Declare subroutine  SRP_Stopwatch, Set_Status, RList, Work_Order_Services, SQL_Services, obj_WO_Mat, obj_Notes
Declare subroutine  SRP_Rotate_Array, SRP_DateTime, obj_WO_Log, Hold_Services

LogPath     = Environment_Services('GetApplicationRootPath') : '\LogFiles\MUBox'
LogDate     = Oconv(Date(), 'D4/')
LogTime     = Oconv(Time(), 'MTS')
LogFileName = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' Makeup Auto Hold Log.csv'
Headers     = 'Logging DTM' : @FM : 'WONo' : @FM : 'Notes'
objLog      = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, Comma$, Headers, '', False$, False$)
LoggingDTM  = LogDate : ' ' : LogTime

LogPath     = Environment_Services('GetApplicationRootPath') : '\LogFiles\WO_Mat'
LogFileName = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' RDS Erase Attempt.csv'
Headers     = 'Logging DTM' : @FM : 'User' : @FM : 'RDSNo' : @FM : 'WOMatKeyID' : @FM : 'Call Stack' : @FM : 'Error'
objLog3     = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, Comma$, Headers, '', False$, False$)

LogPath      = Environment_Services('GetApplicationRootPath') : '\LogFiles\WO_Mat'
LogFileName  = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' Curr Status Change.csv'
Headers      = 'Logging DTM' : @FM : 'User' : @FM : 'WOMatKeyID' : @FM : 'Old Status' : @FM : 'New Status'
objStatusLog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, Comma$, Headers, '', False$, False$)

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

Return Response OR ''


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Service Parameter Options
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Options BOOLEAN     = True$, False$
Options IDType  	= 'RDS', 'WO_MAT'


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


//----------------------------------------------------------------------------------------------------------------------
// GetLotHistory
//
// IDType - [Required] Either 'RDS' or 'WO_MAT'. Default is RDS
// LotID - [Required]  RDS ID or WO_MAT ID, corresponding to IDType
// StartDate - (Optional)
// EndDate - (Optional)
//
// Returns a delimited array of Lot History Events and comments.
// 
//----------------------------------------------------------------------------------------------------------------------
Service GetLotHistory(IDType=IDType, LotID, StartDate, EndDate)
	
	LotList = ''
	LotArray = ''
	CommentList = ''
	CommentArray = ''
	ActionList = ''
	ActionArray = ''
	FinalLotArray = ''
	
	
	// No Start/EndDate entered
	If IDType = 'WO_MAT' then
		// Open RDS and WO_MAT records
		WOMatRow     = Database_Services('ReadDataRow', 'WO_MAT', LotID)
		RDSNo   	 = WOMatRow<WO_MAT_RDS_NO$>
		RDSRow       = Database_Services('ReadDataRow', 'RDS', RDSNo)
		
		// Get RDS Comments
		CommentDates = RDSRow<RDS_COMMENT_DATE$>
		CommentUsers = RDSRow<RDS_COMMENT_USER$>
		Comments     = RDSRow<RDS_COMMENT_NOTE$>
		CommentList  = CommentDates :@FM: CommentUsers :@FM:@FM: Comments
		CommentArray = SRP_Rotate_Array(CommentList)
		
		CommentCount = DCount(CommentArray, @FM)
		For I = 1 to CommentCount
			ThisComment = CommentArray<I, 4>
			Begin Case
				Case IndexC(ThisComment, 'Material Taken off Hold', 1)
					CommentArray<I,3> = 'HOLD_OFF'
					
				Case IndexC(ThisComment, 'Material Placed on Hold', 1)
					CommentArray<I,3> = 'HOLD_ON'
					
				Case IndexC(ThisComment, 'Packaging completed for', 1)
					CommentArray<I,3> = 'PACK'
			End Case
		Next I
		
		// WO_MAT Actions
		INVDTMs      = WOMatRow<WO_MAT_INV_DTM$>
		INVUsers     = WOMatRow<WO_MAT_INV_USER$>
		INVActions   = WOMatRow<WO_MAT_INV_ACTION$>
		ActionList   = INVDTMs :@FM: INVUsers :@FM: INVActions :@FM:@FM
		ActionArray  = SRP_Rotate_Array(ActionList)
		// Remove Hold Inventory actions from array because they are populated from RDS commments.
		HoldOffCount = Count(INVActions, 'HOLD_OFF')
		HoldOnCount  = Count(INVActions, 'HOLD_ON')
		PackCount    = Count(INVActions, 'PACK')
		Locate 'HOLD_OFF' in InvActions Using @VM Setting POS then
			ActionArray = Delete(ActionArray, POS, 0, 0)
		end
		Locate 'HOLD_ON' in InvActions Using @VM Setting POS then
			ActionArray = Delete(ActionArray, POS, 0, 0)
		end
		For K = 1 to PackCount
			Locate 'PACK' in InvActions Using @VM Setting POS then
				ActionArray = Delete(ActionArray, POS, 0, 0)
			end
		Next K

	end else
		// RDS is default IDType
		// Open RDS and WO_MAT records
		RDSRow       = Database_Services('ReadDataRow', 'RDS', LotID)
		WOMatKey     = Xlate('RDS', LotID, 'WO_MAT_KEY', 'X')
		WOMatRow     = Database_Services('ReadDataRow', 'WO_MAT', WOMatKey)
		
		// Get RDS Comments
		CommentDates = RDSRow<RDS_COMMENT_DATE$>
		CommentUsers = RDSRow<RDS_COMMENT_USER$>
		Comments = RDSRow<RDS_COMMENT_NOTE$>
		CommentList  = CommentDates :@FM: CommentUsers :@FM:@FM: Comments
		CommentArray = SRP_Rotate_Array(CommentList)
		
		CommentCount = DCount(CommentArray, @FM)
		For I = 1 to CommentCount
			ThisComment = CommentArray<I, 4>
			Begin Case
				Case IndexC(ThisComment, 'Material Taken off Hold', 1)
					CommentArray<I,3> = 'HOLD_OFF'
					
				Case IndexC(ThisComment, 'Material Placed on Hold', 1)
					CommentArray<I,3> = 'HOLD_ON'
					
				Case IndexC(ThisComment, 'Packaging completed for', 1)
					CommentArray<I,3> = 'PACK'
			End Case
		Next I
		
		// WO_MAT Actions
		INVDTMs      = WOMatRow<WO_MAT_INV_DTM$>
		INVUsers     = WOMatRow<WO_MAT_INV_USER$>
		INVActions   = WOMatRow<WO_MAT_INV_ACTION$>
		ActionList   = INVDTMs :@FM: INVUsers :@FM: INVActions :@FM:@FM
		ActionArray  = SRP_Rotate_Array(ActionList)
		HoldOffCount = Count(INVActions, 'HOLD_OFF')
		HoldOnCount  = Count(INVActions, 'HOLD_ON')
		PackCount    = Count(INVActions, 'PACK')

		// Remove Hold Inventory actions from array because they are populated from RDS commments.
		
		Locate 'HOLD_OFF' in InvActions Using @VM Setting POS then
			ActionArray = Delete(ActionArray, POS, 0, 0)
		end
		Locate 'HOLD_ON' in InvActions Using @VM Setting POS then
			ActionArray = Delete(ActionArray, POS, 0, 0)
		end
		For K = 1 to PackCount
			Locate 'PACK' in InvActions Using @VM Setting POS then
				ActionArray = Delete(ActionArray, POS, 0, 0)
			end
		Next K
	end
	// Add Comments if present
	If CommentArray NE '' then
		LotArray	  = CommentArray :@FM: ActionArray
	end else
		LotArray = ActionArray
	end
	// Sort by Date
	SortedLotArray = SRP_Array('SortRows', LotArray, 'D1', 'LIST')
	
	// Convert DateTimes to External
	RowCount = Dcount(SortedLotArray, @FM)
	If StartDate NE '' AND EndDate NE '' then
		For index = 1 to RowCount
			If SortedLotArray<index,1> LE EndDate and SortedLotArray<index,1> GE StartDate then
				FinalLotArray<-1> = Oconv(SortedLotArray<index,1>, 'DT') :@vm: SortedLotArray<index,2> :@vm: SortedLotArray<index,3> :@vm: SortedLotArray<index,4>
			end
		Next Index
	end else
		For index = 1 to RowCount
			FinalLotArray<-1> = Oconv(SortedLotArray<index,1>, 'DT') :@vm: SortedLotArray<index,2> :@vm: SortedLotArray<index,3> :@vm: SortedLotArray<index,4>
		Next Index
	end	
	
	Response	  = FinalLotArray
	
End Service


//----------------------------------------------------------------------------------------------------------------------
// ProcessAutoHold
//
// looks for MakeUp wafers older than 3 years old and then puts them on hold and sends notification.
//----------------------------------------------------------------------------------------------------------------------
Service ProcessAutoHold()

	hSysLists = Database_Services('GetTableHandle', 'SYSLISTS')
	Lock hSysLists, ServiceKeyID then 
		
		LogData    = ''
		LogData<1> = OConv(Datetime(), 'DT2/^H')
		LogData<2> = 'Begin ':Service
		Logging_Services('AppendLog', objLog, LogData, @RM, @FM)
		
		HoldList            = ''
		FailedHoldList      = ''
		HoldEntityIds       = ''

		CuttoffDtm     = SRP_Datetime('AddYears', Datetime(), -3)
		Query          = 'SELECT MAKEUP_WAFERS WITH UNLOAD_DTM LT ':Quote(OConv(CuttoffDtm, 'DT2/^H')): ' AND WITH EXPIRED NE ':True$
		Flag           = ''
		RList(Query, TARGET_ACTIVELIST$, '', '', Flag)
		
		LogData    = ''
		LogData<1> = OConv(Datetime(), 'DT2/^H')
		LogData<2> = 'RList Flag = ':Flag:'. @RecCount = ':@RecCount
		Logging_Services('AppendLog', objLog, LogData, @RM, @FM)
		
		EOF        = False$
		Loop
			Readnext WOMatKey else EOF = True$
		Until EOF
			
			ErrorMsg    = '' ; // Local loop error message tracking
			ExpiredFlag = ''
			HoldFlag    = ''
			
			LogData 	= ''
			LogData<1>  = OConv(Datetime(), 'DT2/^H')
			LogData<2>  = 'Identified lot "':WOMatKey:'" as older than three years. Checking hold state...'
			Logging_Services('AppendLog', objLog, LogData, @RM, @FM)
			
			WONo      = Field(WOMatKey, '*', 1)
			ReactType = Xlate('WO_LOG', WONo, 'REACT_TYPE', 'X')
			If ReactType EQ 'EPP' then
				CassNo       = Field(WOMatKey, '*', 2)
				HoldEntity   = 'WM_OUT'
				HoldEntityID = WONo:'*1*':CassNo
			end else
				HoldEntity   = 'RDS'
				HoldEntityID = Xlate('WO_MAT', WOMatKey, 'RDS_NO', 'X')
			end
			
			WOMatRec    = Database_Services('ReadDataRow', 'WO_MAT', WOMatKey)
			If Error_Services('NoError') then
				HoldFlag = WOMatRec<WO_MAT_HOLD$>
				If HoldFlag EQ True$ then
					LogData    = ''
					LogData<1> = OConv(Datetime(), 'DT2/^H')
					LogData<2> = 'Lot "':WOMatKey:'" already on hold. Hold flag = ':HoldFlag:'.'
					Logging_Services('AppendLog', objLog, LogData, @RM, @FM)					
				end else
					LogData    = ''
					LogData<1> = OConv(Datetime(), 'DT2/^H')
					LogData<2> = 'Lot "':WOMatKey:'" not on hold. Hold flag = ':HoldFlag:'. Attempting to place lot on hold...'
					Logging_Services('AppendLog', objLog, LogData, @RM, @FM)
					
					// Toggle hold state
					Hold_Services('ToggleHold', WOMatKey, HoldEntity, HoldEntityID, '', 'H', '', 'SYSTEM')
					If Error_Services('NoError') then
						// Verify hold state
						// Re-read record to verify hold state
						WOMatRec = Database_Services('ReadDataRow', 'WO_MAT', WOMatKey)
						If Error_Services('NoError') then
							NewHoldFlag = WOMatRec<WO_MAT_HOLD$>
							LogData     = ''
							LogData<1>  = OConv(Datetime(), 'DT2/^H')
							LogData<2>  = 'Lot "':WOMatKey:'" new hold flag = ':NewHoldFlag:'.'
							Logging_Services('AppendLog', objLog, LogData, @RM, @FM)
							If NewHoldFlag EQ HoldFlag then
								// Something went wrong somewhere
								ErrorMsg   = 'New hold state, ':NewHoldFlag:', of lot "':WOMatKey:'" equals previous hold state, ':HoldFlag:'.'
								LogData    = ''
								LogData<1> = OConv(Datetime(), 'DT2/^H')						
								LogData<2> = ErrorMsg
								Logging_Services('AppendLog', objLog, LogData, @RM, @FM)								
							end
						end else
							ErrorMsg    = Error_Services('GetMessage')
							LogMsg      = 'Failed to verify lot "':WOMatKey:'" was placed on hold.'
							LogMsg     := 'Error message: ':ErrorMsg
							LogData     = ''
							LogData<1>  = OConv(Datetime(), 'DT2/^H')						
							LogData<2>  = LogMsg
							Logging_Services('AppendLog', objLog, LogData, @RM, @FM)								
						end
					end else
						ErrorMsg    = Error_Services('GetMessage')
						LogMsg      = 'Failed to toggle hold state of lot "':WOMatKey:'".'
						LogMsg     := 'Error message: ':ErrorMsg
						LogData     = ''
						LogData<1>  = OConv(Datetime(), 'DT2/^H')						
						LogData<2>  = LogMsg
						Logging_Services('AppendLog', objLog, LogData, @RM, @FM)							
					end
				end
				If ErrorMsg EQ '' then
					// Now mark cassette as expired
					MWRec = Database_Services('ReadDataRow', 'MAKEUP_WAFERS', WOMatKey)
					ExpiredFlag = MWRec<MAKEUP_WAFERS.EXPIRED$>
					LogData    = ''
					LogData<1> = OConv(Datetime(), 'DT2/^H')
					LogData<2> = 'Checking expiry state of "':WOMatKey:'". Expired flag = ':ExpiredFlag:'.'
					Logging_Services('AppendLog', objLog, LogData, @RM, @FM)					
					If ExpiredFlag NE True$ then 
						LogData    = ''
						LogData<1> = OConv(Datetime(), 'DT2/^H')
						LogData<2> = 'Lot "':WOMatKey:'" not marked as expired. Attempting to mark lot "':WOMatKey:'" as expired.'
						Logging_Services('AppendLog', objLog, LogData, @RM, @FM)
						Material_Services('MarkMUCassExpired', WOMatKey)
						If Error_Services('NoError') then
							MWRec = Database_Services('ReadDataRow', 'MAKEUP_WAFERS', WOMatKey)
							If Error_Services('NoError') then
								ExpiredFlag = MWRec<MAKEUP_WAFERS.EXPIRED$>
								LogData     = ''
								LogData<1>  = OConv(Datetime(), 'DT2/^H')
								LogData<2>  = 'Successfully marked lot "':WOMatKey:'" as expired. Expired flag = ':ExpiredFlag:'.'
								Logging_Services('AppendLog', objLog, LogData, @RM, @FM)							
							end else
								ErrorMsg    = Error_Services('GetMessage')
								LogMsg      = 'Failed to verify lot "':WOMatKey:'" was marked as expired.'
								LogMsg     := 'Error message: ':ErrorMsg
								LogData     = ''
								LogData<1>  = OConv(Datetime(), 'DT2/^H')						
								LogData<2>  = LogMsg
								Logging_Services('AppendLog', objLog, LogData, @RM, @FM)	
							end
						end else
							ErrorMsg    = Error_Services('GetMessage')
							LogMsg      = 'Failed to mark lot "':WOMatKey:'" as expired.'
							LogMsg     := 'Error message: ':ErrorMsg
							LogData     = ''
							LogData<1>  = OConv(Datetime(), 'DT2/^H')						
							LogData<2>  = LogMsg
							Logging_Services('AppendLog', objLog, LogData, @RM, @FM)						
						end
					end else
						LogData    = ''
						LogData<1> = OConv(Datetime(), 'DT2/^H')
						LogData<2> = 'Lot "':WOMatKey:'" already marked as expired. Expired flag = ':ExpiredFlag:'.'
						Logging_Services('AppendLog', objLog, LogData, @RM, @FM)						
					end
				end else
					ErrorMsg    = Error_Services('GetMessage')
					LogMsg      = 'Failed to toggle hold state of lot "':WOMatKey:'".'
					LogMsg     := 'Error message: ':ErrorMsg
					LogData     = ''
					LogData<1>  = OConv(Datetime(), 'DT2/^H')						
					LogData<2>  = LogMsg
					Logging_Services('AppendLog', objLog, LogData, @RM, @FM)					
				end
			end else
				ErrorMsg    = Error_Services('GetMessage')
				LogMsg      = 'Failed to place lot "':WOMatKey:'" on hold. Error reading WO_MAT record to get hold state.'
				LogMsg     := 'Error message: ':ErrorMsg
				LogData     = ''
				LogData<1>  = OConv(Datetime(), 'DT2/^H')
				LogData<2>  = LogMsg				
				Logging_Services('AppendLog', objLog, LogData, @RM, @FM)				
			end

			If (ErrorMsg EQ '') then
				HoldList<-1>      = ReactType:TAB$:HoldEntityID
				HoldEntityIds<-1> = WOMatKey
			end else
				FailedHoldList<-1>      = ReactType:TAB$:HoldEntityID
			end
			
		Repeat
		
		NotifyDtm = Datetime()
		
		// Send initial notification of successful lots and failed lots
		If (HoldEntityIds NE '') then
			Recipients    = ''
			SentFrom      = 'SYSTEM'
			Subject       = 'Makeup Wafer Auto-Hold Report'
			AttachWindow  = ''
			AttachKey     = ''
			SendToGroup   = 'AUTO_HOLD'
			Message       = 'The following makeup cassettes are older than three years and have been automatically '
			Message      := 'placed on hold.':CRLF$:'Reminder: EpiPro lots can contain multiple RDS lots.':CRLF$:CRLF$
			Swap @FM with CRLF$ in HoldList
			Message      := HoldList
			Parms         = Recipients:@RM:SentFrom:@RM:Subject:@RM:Message:@RM:AttachWindow:@RM:AttachKey:@RM:SendToGroup
			obj_Notes('Create',Parms)
			
			For each HoldEntityId in HoldEntityIds using @FM
				WOMatKey = HoldEntityId	
				MUWfrRec = Database_Services('ReadDataRow', 'MAKEUP_WAFERS', WOMatKey)
				If Error_Services('NoError') then
					HaveLock = Database_Services('GetKeyIDLock', 'MAKEUP_WAFERS', WOMatKey, True$)
					If HaveLock then 
						MUWfrRec<MAKEUP_WAFERS.EXPIRY_LAST_NOTIFY_DTM$> = NotifyDtm
						Database_Services('WriteDataRow', 'MAKEUP_WAFERS', WOMatKey, MUWfrRec, True$, False$, False$)
						If Error_Services('NoError') then
							LogData    = ''
							LogData<1> = OConv(Datetime(), 'DT2/^H')
							LogData<2> = 'Updated expiry notify dtm for expired lot "':WOMatKey:'".'
							Logging_Services('AppendLog', objLog, LogData, @RM, @FM)							
						end else
							ErrorMsg    = Error_Services('GetMessage')
							LogMsg      = 'Failed to update expiry notify dtm for expired lot "':WOMatKey:'". Error updating MAKEUP_WAFERS record.'
							LogMsg     := 'Error message: ':ErrorMsg
							LogData     = ''
							LogData<1>  = OConv(Datetime(), 'DT2/^H')
							LogData<2>  = LogMsg				
							Logging_Services('AppendLog', objLog, LogData, @RM, @FM)
							SelfLocked  = Database_Services('IsKeyIDSelfLocked', 'MAKEUP_WAFERS', WOMatKey)
							If SelfLocked then Database_Services('ReleaseKeyIDLock', 'MAKEUP_WAFERS', WOMatKey)
						end
					end else
						LogMsg      = 'MAKEUP_WAFERS record for expired lot "':WOMatKey:'" is locked. Service will attempt again on next service call.'
						LogMsg     := 'Error message: ':ErrorMsg
						LogData     = ''
						LogData<1>  = OConv(Datetime(), 'DT2/^H')
						LogData<2>  = LogMsg				
						Logging_Services('AppendLog', objLog, LogData, @RM, @FM)						
					end
				end else
					ErrorMsg    = Error_Services('GetMessage')
					LogMsg      = 'Failed to send notification for expired lot "':WOMatKey:'". Error reading MAKEUP_WAFERS record.'
					LogMsg     := 'Error message: ':ErrorMsg
					LogData     = ''
					LogData<1>  = OConv(Datetime(), 'DT2/^H')
					LogData<2>  = LogMsg				
					Logging_Services('AppendLog', objLog, LogData, @RM, @FM)				
				end				
			Next HoldEntityId
		end
		
		If (FailedHoldList NE '') then
			Recipients    = ''
			SentFrom      = 'SYSTEM'
			Subject       = 'Makeup Wafer Auto-Hold Report'
			AttachWindow  = ''
			AttachKey     = ''
			SendToGroup   = 'AUTO_HOLD'
			Message       = 'The following makeup cassettes are older than three years and failed to be placed on hold by '
			Message      := 'the system.':CRLF$:'Reminder: EpiPro lots can contain multiple RDS lots.':CRLF$:CRLF$
			Swap @FM with CRLF$ in FailedHoldList
			Message      := FailedHoldList
			Parms         = Recipients:@RM:SentFrom:@RM:Subject:@RM:Message:@RM:AttachWindow:@RM:AttachKey:@RM:SendToGroup
			obj_Notes('Create',Parms)			
		end
		
		// Send reminders of expired lots not yet NCR'd, which should trigger deletion of MAKEUP_WAFERS record when quantity reaches zero.
		NotifyList   = ''
		ThresholdDtm = SRP_Datetime('AddHours', Datetime(), -12)
		ThresholdDtm = OConv(ThresholdDtm, 'DT2/^H')
		Query        = 'SELECT MAKEUP_WAFERS WITH EXPIRED EQ ':True$:' AND WITH EXPIRY_LAST_NOTIFY_DTM LT ':Quote(ThresholdDtm)
		Flag         = ''
		RList(Query, TARGET_ACTIVELIST$, '', '', Flag)
		
		LogData      = ''
		LogData<1>   = OConv(Datetime(), 'DT2/^H')
		LogData<2>   = 'Notification RList Flag = ':Flag:'. @RecCount = ':@RecCount
		Logging_Services('AppendLog', objLog, LogData, @RM, @FM)
		
		EOF        = False$
		Loop
			Readnext WOMatKey else EOF = True$
		Until EOF
			WONo      = Field(WOMatKey, '*', 1)
			ReactType = Xlate('WO_LOG', WONo, 'REACT_TYPE', 'X')
			If ReactType EQ 'EPP' then
				CassNo       = Field(WOMatKey, '*', 2)
				HoldEntityID = WONo:'*1*':CassNo
			end else
				HoldEntityID = Xlate('WO_MAT', WOMatKey, 'RDS_NO', 'X')
			end			
			MUWfrRec     = Database_Services('ReadDataRow', 'MAKEUP_WAFERS', WOMatKey)
			If Error_Services('NoError') then
				HaveLock = Database_Services('GetKeyIDLock', 'MAKEUP_WAFERS', WOMatKey, True$)
				If HaveLock then 
					MUWfrRec<MAKEUP_WAFERS.EXPIRY_LAST_NOTIFY_DTM$> = NotifyDtm
					Database_Services('WriteDataRow', 'MAKEUP_WAFERS', WOMatKey, MUWfrRec, True$, False$, False$)
					If Error_Services('NoError') then
						NotifyList<-1> = ReactType:TAB$:HoldEntityID
						LogData        = ''
						LogData<1>     = OConv(Datetime(), 'DT2/^H')
						LogData<2>     = 'Updated expiry notify dtm for expired lot "':WOMatKey:'".'
						Logging_Services('AppendLog', objLog, LogData, @RM, @FM)						
					end else
						ErrorMsg    = Error_Services('GetMessage')
						LogMsg      = 'Failed to send notification for expired lot "':WOMatKey:'". Error updating MAKEUP_WAFERS record.'
						LogMsg     := 'Error message: ':ErrorMsg
						LogData     = ''
						LogData<1>  = OConv(Datetime(), 'DT2/^H')
						LogData<2>  = LogMsg				
						Logging_Services('AppendLog', objLog, LogData, @RM, @FM)
						SelfLocked = Database_Services('IsKeyIDSelfLocked', 'MAKEUP_WAFERS', WOMatKey)
						If SelfLocked then Database_Services('ReleaseKeyIDLock', 'MAKEUP_WAFERS', WOMatKey)
					end
				end else
					LogMsg      = 'MAKEUP_WAFERS record for expired lot "':WOMatKey:'" is locked. Service will attempt again on next service call.'
					LogMsg     := 'Error message: ':ErrorMsg
					LogData     = ''
					LogData<1>  = OConv(Datetime(), 'DT2/^H')
					LogData<2>  = LogMsg				
					Logging_Services('AppendLog', objLog, LogData, @RM, @FM)						
				end
			end else
				ErrorMsg    = Error_Services('GetMessage')
				LogMsg      = 'Failed to send notification for expired lot "':WOMatKey:'". Error reading MAKEUP_WAFERS record.'
				LogMsg     := 'Error message: ':ErrorMsg
				LogData     = ''
				LogData<1>  = OConv(Datetime(), 'DT2/^H')
				LogData<2>  = LogMsg				
				Logging_Services('AppendLog', objLog, LogData, @RM, @FM)				
			end
		Repeat
		
		If (NotifyList NE '') then
			Recipients    = ''
			SentFrom      = 'SYSTEM'
			Subject       = 'Makeup Wafer Auto-Hold Report'
			AttachWindow  = ''
			AttachKey     = ''
			SendToGroup   = 'AUTO_HOLD'
			Message       = 'The following makeup cassettes are older than three years and have been automatically '
			Message      := 'placed on hold.':CRLF$:'Reminder: EpiPro lots can contain multiple RDS lots.':CRLF$:CRLF$
			Swap @FM with CRLF$ in NotifyList
			Message      := NotifyList
			Parms         = Recipients:@RM:SentFrom:@RM:Subject:@RM:Message:@RM:AttachWindow:@RM:AttachKey:@RM:SendToGroup
			obj_Notes('Create',Parms)
		end
		
		LogData    = ''
		LogData<1> = OConv(Datetime(), 'DT2/^H')
		LogData<2> = 'End ':Service
		Logging_Services('AppendLog', objLog, LogData, @RM, @FM)		
		
		Unlock hSysLists, ServiceKeyID else Null
	end
	
end service


//----------------------------------------------------------------------------------------------------------------------
// GetAvailableMakeupWafers
//
// Returns an array of available makeup wafers for the indicated work order.
//
// <1> = Cassettes with final RDS signature (i.e., available to use).
// <2> = Cassettes still missing final RDS signature.
//
// Data within each field mark will be @VM row and @SVM column limited.
//----------------------------------------------------------------------------------------------------------------------
Service GetAvailableMakeupWafers(WorkOrderNo, KeysOnly=BOOLEAN)

	AvailableMakeupWafers = ''
	If WorkOrderNo NE '' then
		WorkOrderNo = WorkOrderNo[1, '*']   ; // This might be formatted as a WO_MAT Key ID, so strip off the Cassette No.
		WOLogRow    = Database_Services('ReadDataRow', 'WO_LOG', WorkOrderNo)
		If Error_Services('NoError') then
			WOStepKeys    = WOLogRow<WO_LOG_WO_STEP_KEY$>
			LastWOStepKey = WOStepKeys[-1, 'B' : @VM]
			PartNo        = WOLogRow<WO_LOG_EPI_PART_NO$>
			WOStepRow     = Database_Services('ReadDataRow', 'WO_STEP', LastWOStepKey)
			If Error_Services('NoError') then
				WOStepPSN       = WOStepRow<WO_STEP_PROD_SPEC_ID$>
				WOStepRDSNos    = WOStepRow<WO_STEP_RDS_KEY$>
				WOStepWMOutNos  = WOStepRow<WO_STEP_WM_OUT_KEYS$>
				ProdSpecRow     = Database_Services('ReadDataRow', 'PROD_SPEC', WOStepPSN)
				If Error_Services('NoError') then
					ReactorType       = ProdSpecRow<PROD_SPEC_REACTOR_TYPE$>
					SearchString      = ''
					SearchTable       = 'MAKEUP_WAFERS'
					If ReactorType EQ 'EPP' then
						SearchString     := 'EPI_PART_NO' : @VM : PartNo : @FM : 'REACT_TYPE' : @VM : 'EPP' : @FM 
					end else
						SearchString     := 'EPI_PART_NO' : @VM : PartNo : @FM : 'REACT_TYPE' : @VM : '#EPP' : @FM
					end
					
					hDictSearchTable  = Database_Services('GetTableHandle', 'DICT.' : SearchTable)
					If Error_Services('NoError') then
						rv              = Set_Status(0)
						MakeupBoxes     = ''
						Flag            = ''
						StatusCode      = ''
						Btree.Extract(SearchString, SearchTable, hDictSearchTable, MakeupBoxes, '', Flag)
						If Get_Status(StatusCode) then
							Error_Services('Add', 'Error calling Btree.Extract in the ' : Service : ' service. StatusCode: ' : StatusCode)
						end else
							If MakeupBoxes NE '' then
								// One or more makeup was found that matches the customer's part and has the status of 'Ready to Use'.
								// Now loop through to find out which ones have the RDS final signature or not.
								NumMakeupBoxes  = DCount(MakeupBoxes, @VM)
								For Each MakeupBoxID in MakeupBoxes using @VM
									WOMatKeyID = MakeupBoxID
									CurrStatus = Xlate('MAKEUP_WAFERS', MakeupBoxID, 'CURR_STATUS_STATIC', 'X')
									// DPC 7/21/20 added HOLD as excluded status type
									Locate CurrStatus in 'MT,VOID,SHIP,REJ,HOLD' using ',' setting cPos else
										If (ReactorType EQ 'EPP') OR (ReactorType EQ 'P') then
											CurrWaferCount  = obj_WO_Mat('CurrWaferCnt', WOMatKeyID:@RM:'':@RM:True$)
										end else
											CurrWaferCount  = obj_WO_Mat('CurrWaferCnt', WOMatKeyID)
										end
										If CurrWaferCount GT 0 then
											WOMatRow         = Database_Services('ReadDataRow', 'WO_MAT', WOMatKeyID)
											RDSKeyIDs        = WOMatRow<WO_MAT_RDS_NO$>
											SAPBatchNo       = WOMatRow<WO_MAT_SAP_BATCH_NO$>
											FirstRDSKeyID    = RDSKeyIDs[1, @VM]
											RDSRow           = Database_Services('ReadDataRow', 'RDS', FirstRDSKeyID)
											WMOKey           = WOMatRow<WO_MAT_WMO_KEY$>
											If WMOKey NE '' then
												WMOFQASig = Xlate('WM_OUT', WMOKey, 'SUP_VER_SIG', 'X')
											end else
												WMOFQASig = ''
											end
											ThisWorkOrderNo  = WOMatKeyID[1, '*']
											ThisCassetteNo   = WOMatKeyID[Col2() + 1, '*']
											ThisWOLogRow     = Database_Services('ReadDataRow', 'WO_LOG', ThisWorkOrderNo)
											ThisWOStepKeyID  = ThisWOLogRow<WO_LOG_WO_STEP_KEY$>[-1, 'B' : @VM]
											ThisWOStepRow    = Database_Services('ReadDataRow', 'WO_STEP', ThisWOStepKeyID)
											WOStepRDSNos     = ThisWoStepRow<WO_STEP_RDS_KEY$>
											MakeupBoxDet     = ThisWorkOrderNo : @SVM
											MakeupBoxDet    := ThisCassetteNo : @SVM
											MakeupBoxDet    := WOMatRow<WO_MAT_SAP_BATCH_NO$> : @SVM
											MakeupBoxDet    := ThisWOStepRow<WO_STEP_PROD_SPEC_ID$> : @SVM
											MakeupBoxDet    := PartNo : @SVM
											MakeupBoxDet    := CurrWaferCount : @SVM
											MakeupBoxDet    := FirstRDSKeyID
											// JRO Added PROD_SPEC PARAMETERS 5/8/2020
											If (ReactorType EQ 'EPP') OR (ReactorType EQ 'P') then
												ProdSpecID = Xlate('RDS', WOStepRDSNos<1, 1>, RDS_PROD_SPEC_ID$, 'X')
											end else
												ProdSpecID = Xlate('RDS', RDSKeyIDs, RDS_PROD_SPEC_ID$, 'X')
											end
											ActiveStatus = XLATE('PROD_SPEC', ProdSpecID, PROD_SPEC_STATUS$, 'X')
											LabelCheck = Count(WOMatRow<WO_MAT_INV_ACTION$>, 'LBLCHK')
											// JRO Added the extra condition for Active Status 5/8/2020
											If ActiveStatus EQ 'A' then
												Begin Case
													
													Case SAPBatchNo EQ ''
														// Cassette has not received a batch number from SAP, so it is not yet eligible to be used.
														FieldPos = 3
														
													Case ( (ReactorType NE 'EPP') and (RDSRow<RDS_SUP_VER_SIG$> NE '') and  (LabelCheck NE FALSE$) ) OR ( (ReactorType EQ 'EPP') and (WMOFQASig NE '') and (LabelCheck NE FALSE$) )
														// Cassette has a final signature, so it is eligible to be used.
														FieldPos = 1
														
													Case ( (ReactorType NE 'EPP') and (RDSRow<RDS_SUP_VER_SIG$> EQ '') ) OR ( (ReactorType EQ 'EPP') and (WMOFQASig EQ '') )
														// Cassette does not yet have a final signature, so it is not yet eligible to be used.
														FieldPos = 2
														
													Case ( (ReactorType NE 'EPP') and (RDSRow<RDS_SUP_VER_SIG$> NE '') and (LabelCheck EQ False$)) OR ( (ReactorType EQ 'EPP') and (WMOFQASig NE '') and (LabelCheck EQ False$))
														// Cassette does not yet have a packing label check, so it is not yet eligible to be used.
														FieldPos = 4
														
													Case Otherwise$
														// Log these cassettes as we should never end up here.
														Null
														
												End Case
												
												If KeysOnly EQ True$ then
													If (ReactorType EQ 'EPP') OR (ReactorType EQ 'P') then
														WMOFormat             = Field(MakeupBoxID, '*', 1):'*1*':Field(MakeupBoxID, '*', 2)
														AvailableMakeupWafers = Insert(AvailableMakeupWafers, FieldPos, -1, 0, WMOFormat)
													end else
														AvailableMakeupWafers   = Insert(AvailableMakeupWafers, FieldPos, -1, 0, WOMatKeyID)
													end
												end else
													AvailableMakeupWafers   = Insert(AvailableMakeupWafers, FieldPos, -1, 0, MakeupBoxDet)
												end
												
											end
										end
									end
								Next MakeupBoxID
							end
						end
					end
				end
			end
		end
	end else
		Error_Services('Add', 'WorkOrderNo argument was missing from the ' : Service : ' service.')
	end
	
	Response    = AvailableMakeupWafers
	
end service


Service UpdateWOMatStatus(WOMatKey)
	
	If WOMatKey NE '' then
		WOMatRec        = Database_Services('ReadDataRow', 'WO_MAT', WOMatKey)
		WOMatCurrStatus = obj_WO_Mat('CurrStatus', WOMatKey:@RM:WOMatRec)
		WOMatCurrStatus = WOMatCurrStatus[-1, 'B':@VM]
		// Get a fresh copy of the record.
		WOMatRec              = Database_Services('ReadDataRow', 'WO_MAT', WOMatKey)
		WOMatCurrStatusStatic = WOMatRec<WO_MAT_CURR_STATUS_STATIC$>
		If WOMatCurrStatus NE WOMatCurrStatusStatic then
			WOMatRec<WO_MAT_CURR_STATUS_STATIC$> = WOMatCurrStatus
			Database_Services('WriteDataRow', 'WO_MAT', WOMatKey, WOMatRec, True$, False$, True$)
			LogData = ''
			LogData<1> = LoggingDTM
			LogData<2> = @User4
			LogData<3> = WOMatKey
			LogData<4> = WOMatCurrStatusStatic
			LogData<5> = WOMatCurrStatus
			Logging_Services('AppendLog', objStatusLog, LogData, @RM, @FM)
		end
		Database_Services('ReleaseKeyIDLock', 'WO_MAT', WOMatKey)
	end
	
end service


Service PostWOMatUpdateRequest(WOMatKey)
	
	If WOMatKey NE '' then
		// Look for a pre-existing request in the queue
		If (RowExists('WO_MAT_QUEUE', WOMatKey) EQ False$) then
			// This is a new request
			RequestKeyID                          = WOMatKey
			RequestRow                            = ''
			RequestRow<WO_MAT_QUEUE.REQUEST_DTM$> = Datetime()
			Database_Services('WriteDataRow', 'WO_MAT_QUEUE', RequestKeyID, RequestRow, False$, False$, False$)
		end else
			// Duplicate request
			Null
		end
	end else
		Error_Services('Add', 'Null parameter passed into service call. All parameters are required.')
	end
	
end service


Service ProcessWOMatUpdateRequests()
	
	hSysLists   = Database_Services('GetTableHandle', 'SYSLISTS')
	Lock hSysLists, ServiceKeyID then
		Open 'WO_MAT_QUEUE' to hTable then
			Select hTable
			EOF = False$
			Loop
				ReadNext RequestKeyID else EOF = True$
			Until EOF EQ True$
				WOMatKey = RequestKeyID
				Material_Services('UpdateWOMatStatus', WOMatKey)
				If Error_Services('NoError') then
					// Successfully updated the current status, so delete the request from the queue.
					Database_Services('DeleteDataRow', 'WO_MAT_QUEUE', RequestKeyID, True$, True$)
				end
			Repeat
		end else
			ErrorMsg = 'Error opening WO_MAT_QUEUE in ':Service:' service.'
		end
		Unlock hSysLists, ServiceKeyID else Null    
	end
	
end service


Service UpdateWMOutStatus(WMOKey)
	
	If WMOKey NE '' then
		WMOutRec                             = Database_Services('ReadDataRow', 'WM_OUT', WMOKey)
		WMOutCurrStatus                      = obj_WM_Out('CurrStatus', WMOKey:@RM:WMOutRec)
		// Get a fresh copy of the record
		WMOutRec              = Database_Services('ReadDataRow', 'WM_OUT', WMOKey)
		WMOutCurrStatusStatic = WMOutRec<WM_OUT_CURR_STATUS_STATIC$>
		If WMOutCurrStatus NE WMOutCurrStatusStatic then
			WMOutRec<WM_OUT_CURR_STATUS_STATIC$> = WMOutCurrStatus
			Database_Services('WriteDataRow', 'WM_OUT', WMOKey, WMOutRec, True$, False$, True$)
		end
		Database_Services('ReleaseKeyIDLock', 'WM_OUT', WMOKey)
	end
	
end service


Service UpdateRDSStatus(RDSNo)
	
	If RDSNo NE '' then
		RDSCombStatus = Xlate('RDS', RDSNo, 'COMB_STATUS', 'X')
		// Get a fresh copy of the record
		RDSRec              = Database_Services('ReadDataRow', 'RDS', RDSNo)
		RDSCombStatusStatic = RDSRec<RDS_COMB_STATUS_STATIC$>
		If RDSCombStatus NE RDSCombStatusStatic then
			RDSRec<RDS_COMB_STATUS_STATIC$> = RDSCombStatus
			Database_Services('WriteDataRow', 'RDS', RDSNo, RDSRec, True$, True$, True$)
		end	
	end
	
end service


Service PostRDSUpdateRequest(RDSNo)
	
	If RDSNo NE '' then
		// Look for a pre-existing request in the queue
		If (RowExists('WM_OUT_QUEUE', RDSNo) EQ False$) then 
			// This is a new request
			RequestKeyID                       = RDSNo
			RequestRow                         = ''
			RequestRow<RDS_QUEUE.REQUEST_DTM$> = Datetime()
			Database_Services('WriteDataRow', 'RDS_QUEUE', RequestKeyID, RequestRow, False$, False$, False$)
		end else
			// Duplicate request - ignore it.
			Null
		end
	end else
		Error_Services('Add', 'Null parameter passed into service call. All parameters are required.')
	end
	
end service


Service ProcessRDSUpdateRequests()
	
	hSysLists   = Database_Services('GetTableHandle', 'SYSLISTS')
	Lock hSysLists, ServiceKeyID then
		Open 'RDS_QUEUE' to hTable then
			Select hTable
			EOF = False$
			Loop
				ReadNext RequestKeyID else EOF = True$
			Until EOF EQ True$
				RDSNo = RequestKeyID
				Material_Services('UpdateRDSStatus', RDSNo)
				If Error_Services('NoError') then
					// Successfully updated the current status, so delete the request from the queue.
					Database_Services('DeleteDataRow', 'RDS_QUEUE', RequestKeyID, True$, True$)
				end
			Repeat
		end else
			ErrorMsg = 'Error opening RDS_QUEUE in ':Service:' service.'
		end
		Unlock hSysLists, ServiceKeyID else Null    
	end
	
end service


Service PostWMOutUpdateRequest(WMOKey)
	
	If WMOKey NE '' then
		// Look for a pre-existing request in the queue
		If (RowExists('WM_OUT_QUEUE', WMOKey) EQ False$) then 
			// This is a new request
			RequestKeyID                          = WMOKey
			RequestRow                            = ''
			RequestRow<WM_OUT_QUEUE.REQUEST_DTM$> = Datetime()
			Database_Services('WriteDataRow', 'WM_OUT_QUEUE', RequestKeyID, RequestRow, False$, False$, False$)
		end else
			// Duplicate request - ignore it.
			Null
		end
	end else
		Error_Services('Add', 'Null parameter passed into service call. All parameters are required.')
	end
	
end service


Service ProcessWMOutUpdateRequests()
	
	hSysLists   = Database_Services('GetTableHandle', 'SYSLISTS')
	Lock hSysLists, ServiceKeyID then
		Open 'WM_OUT_QUEUE' to hTable then
			Select hTable
			EOF = False$
			Loop
				ReadNext RequestKeyID else EOF = True$
			Until EOF EQ True$
				WMOKey = RequestKeyID
				Material_Services('UpdateWMOutStatus', WMOKey)
				If Error_Services('NoError') then
					// Successfully updated the current status, so delete the request from the queue.
					Database_Services('DeleteDataRow', 'WM_OUT_QUEUE', RequestKeyID, True$, True$)
				end
			Repeat
		end else
			ErrorMsg = 'Error opening WM_OUT_QUEUE in ':Service:' service.'
		end
		Unlock hSysLists, ServiceKeyID else Null    
	end
	
end service


Service PostWfrLocUpdate(WMOKey)
	
	Swap '.' with '*' in WMOKey
	WONo     = Field(WMOKey, '*', 1)
	CassNo   = Field(WMOKey, '*', 2)
	WOMatKey = WONo:'*':CassNo
	If RowExists('WO_MAT', WOMatKey) then
		WfrList   = Xlate('WO_MAT_WFR', WOMatKey, 'OUT_WFR_ID', 'X')
		GaNWfrIDs = Xlate('WO_WFR', WfrList, 'GAN_WFR_ID', 'X')
		WfrLocs   = Xlate('WO_WFR', WfrList, 'CUST_NAME', 'X')
		For each Wfr in GaNWfrIDs using @VM setting vPos
			LocationName  = WfrLocs<0, vPos>
			GaNWfrID      = GaNWfrIDs<0, vPos>
			SQLStatement  = 'UPDATE [G4Wafers_01].[dbo].[Wafer Location] '
			SQLStatement := "SET Main_Location  = '":LocationName:"', Main_Form = 'FULL' "
			SQLStatement := "WHERE Wafer_ID  = '":GaNWfrID:"'"
			SQL_Services('PostSQLStatement', 'IQSDMS1', SQLStatement)
		Next Wfr
	end
	
end service


Service UpdateWaferLocation(WMOKey)
	
	Swap '.' with '*' in WMOKey
	WONo     = Field(WMOKey, '*', 1)
	CassNo   = Field(WMOKey, '*', 2)
	WOMatKey = WONo:'*':CassNo
	If RowExists('WO_MAT', WOMatKey) then
		WfrList   = Xlate('WO_MAT_WFR', WOMatKey, 'OUT_WFR_ID', 'X')
		GaNWfrIDs = Xlate('WO_WFR', WfrList, 'GAN_WFR_ID', 'X')
		WfrLocs   = Xlate('WO_WFR', WfrList, 'CUST_NAME', 'X')
		objConn   = Sql_Services('GetConnectionObject', 'IQSDMS1')
		For each Wfr in GaNWfrIDs using @VM setting vPos
			LocationName  = WfrLocs<0, vPos>
			GaNWfrID      = GaNWfrIDs<0, vPos>
			SQLStatement  = 'UPDATE [G4Wafers_01].[dbo].[Wafer Location] '
			SQLStatement := "SET Main_Location  = '":LocationName:"', Main_Form = 'FULL' "
			SQLStatement := "WHERE Wafer_ID  = '":GaNWfrID:"'"
			SQL_Services('ExecuteQuery', objConn, SQLStatement, 0, 1)
		Next Wfr
	end
	
end service


Service VerifyEPPUnload(RDSNo)
	
	ErrorMsg = ''
	If RDSNo NE '' then
		// Verify RDS and REACT_RUN outbound cassette data matches.
		RDSRec        = Database_Services('ReadDataRow', 'RDS', RDSNo)
		RDSOutCassNos = RDSRec<RDS_OUT_CASS_NO$>
		RDSOutSlotNos = RDSRec<RDS_OUT_SLOT_NO$>
		RRRec         = Database_Services('ReadDataRow', 'REACT_RUN', RDSNo)
		RROutCassNos  = RRRec<REACT_RUN_OUT_CASS_NO$>
		RROutSlotNos  = RRRec<REACT_RUN_OUT_SLOT_NO$>
		If ( (RDSOutCassNos NE RROutCassNos) or (RDSOutSlotNos NE RROutSlotNos) ) then
			ErrorMsg = 'Error in ':Service:' service. RDS and REACT_RUN mismatch for key ':RDSNo:'!'
		end else
			WONo         = RDSRec<RDS_WO$>
			// Verify WM_OUT outbound cassette data matches
			RDSInCassNos = RDSRec<RDS_IN_CASS_NO$>
			RDSInSlotNos = RDSRec<RDS_IN_SLOT_NO$>
			If RDSOutCassNos NE '' then
				For each OutCassNo in RDSOutCassNos using @VM setting vPos
					// TEST wafers are not placed in outbound cassettes.
					If OutCassNo NE '' then
						// Open WM_OUT record, go to the out slot and verify that the IN_CASS_NO and IN_SLOT_NO match.
						WMOutKey    = WONo:'*1*':OutCassNo
						WMOutRec    = Database_Services('ReadDataRow', 'WM_OUT', WMOutKey)
						OutSlotNo   = RDSOutSlotNos<0, vPos>
						WMOInCassNo = WMOutRec<WM_OUT_IN_CASS_NO$, OutSlotNo>
						WMOInSlotNo = WMOutRec<WM_OUT_IN_SLOT_NO$, OutSlotNo>
						RDSInCassNo = RDSInCassNos<0, vPos>
						RDSInSlotNo = RDSInSlotNos<0, vPos>
						If (WMOInCassNo EQ RDSInCassNo) and (WMOInSlotNo EQ RDSInSlotNo) then
							// Data matches
							Null
						end else
							// Data mismatch - throw error
							ErrorMsg = 'Error in ':Service:' service. RDS and WM_OUT mismatch for inbound wafer ':RDSInCassNo:'*':RDSInSlotNo:'!'
						end
					end
				Until ErrorMsg NE ''
				Next OutCassNo
			end else
				ErrorMsg = 'Error in ':Service:' service. RDS out cassettes numbers are null!'
			end
		end
		
	end else
		ErrorMsg = 'Error in ':Service:' service. Null RDSNo passed in!'
	end
	
	If ErrorMsg NE '' then
		Error_Services('Add', 'Error in ':Service:' service. Error message: ':ErrorMsg)
		Response = False$
	end else
		Response = True$
	end
	
end service


Service ConvertMaterial(WONo, CassNos, NewPVNo)
	
	NewWONo  = ''
	ErrorMsg = ''
	If RowExists('WO_LOG', WONo) then
		If CassNos NE '' then
			Swap @FM with @VM in CassNos
			WOMatKeys = ''
			For each CassNo in CassNos using @VM setting vPos
				WOMatKeys<0, vPos> = WONo:'*':CassNo
				MakeupFlag         = Xlate('WO_MAT', WONo:'*':CassNo, WO_MAT_MAKEUP_BOX$, 'X')
				If MakeupFlag then
					ErrorMsg = 'Error in ':Service:' service. Cassette ':WONo:'*':CassNo:' is a makeup box and cannot be converted to a different part number.'
				end
				If ( (ErrorMsg EQ '') and (Not(RowExists('WO_MAT', WONo:'*':CassNo) ) ) ) then
					ErrorMsg = 'Error in ':Service:' service. Cassette ':WONo:'*':CassNo:' does not exist.'
				end
			Until ErrorMsg NE ''
			Next CassNo
			If ErrorMsg EQ '' then 
				If RowExists('PROD_VER', NewPVNo) then
					* 1. Create new WO with coresponding Part no, PSN, EpiPartNo, Cust Epi Part, Sub Part No, Revised Quantity(Peeled off amount coming back)     
					*                -Subtract MU wafer quantity(original amount that is apart of a MU lot, 1 MU lot = 25 wafers)
					
					// Generate new internal production order number to associate with the new work order.
					NumAttempts = 0
					HaveLock    = False$
					Loop
						NumAttempts += 1
						HaveLock         = Database_Services('GetKeyIDLock', 'APP_INFO', 'CURR_INT_PROD_ORD_NO')
					Until HaveLock or NumAttempts EQ 10
					Repeat
					
					If HaveLock then
						CurrIntProdOrdNo = Database_Services('ReadDataRow', 'APP_INFO', 'CURR_INT_PROD_ORD_NO')
						NextIntProdOrdNo = CurrIntProdOrdNo + 1
						
						NewIntProdOrdNo = 'IFX' : Fmt(NextIntProdOrdNo, 'R(0)#4') : '.1'
						NewEpiPartNo    = Xlate('PROD_VER', NewPVNo, PROD_VER_EPI_PART_NO$, 'X')
						SubPartNo       = Xlate('PROD_VER', NewPVNo, PROD_VER_SUB_PART_NO$, 'X')
						SubPartRev      = Xlate('WO_LOG', WONo, WO_LOG_ORD_SUB_PART_REV$, 'X')
						NewWOQty        = Sum(Xlate('WO_MAT', WOMatKeys, WO_MAT_WAFER_QTY$, 'X'))
						VendCd          = Xlate('WO_LOG', WONo, WO_LOG_EXP_VEND_CD$, 'X')
						NewCustNo       = Xlate('PROD_VER', NewPVNo, PROD_VER_CUST_NO$, 'X')
						NewPSNo         = Xlate('PROD_VER', NewPVNo, PROD_VER_PROC_STEP_PSN$, 'X')
						
						If ( (NewIntProdOrdNo NE '') and (NewEpiPartNo NE '') and (SubPartNo NE '') and (NewWOQty NE '') and (VendCd NE '') and (NewCustNo NE '') and (NewPSNo NE '') ) then 
							MockSAPFile       = ''
							MockSAPFile<0, 1> = NewIntProdOrdNo ; // Prod Ord No       - Must match "'M'6N'.1'" OR "7X'.1'" and not conflict with SAP
							MockSAPFile<0, 2> = 'NA'            ; // CustPONo          - Not Applicable
							MockSAPFile<0, 3> = Date() + 365    ; // Promise Ship Date - Hard coded to a year from today
							MockSAPFile<0, 4> = NewEpiPartNo    ; // EpiPartNo         - Changing from original work order
							MockSAPFile<0, 5> = SubPartNo       ; // SubPartNo         - Not changing from original work order
							MockSAPFile<0, 6> = SubPartRev      ; // SubRevNo          - Not changing from original work order
							MockSAPFile<0, 7> = NewWOQty        ; // WOQty             - Quantity of material moving to new work order
							MockSAPFile<0, 8> = VendCd          ; // VendCd            - Not changing from original work order
							
							Success = obj_WO_Log('SAPCreate', MockSAPFile)
							If Success then
								// obj_WO_Log('SAPCreate') does not return new work order number, so look it up instead.
								SearchString = 'PROD_ORD_NO':@VM:NewIntProdOrdNo:@FM
								Open 'DICT.WO_LOG' to hDict then
									Flag = ''
									Btree.Extract(SearchString, 'WO_LOG', hDict, NewWONo, 'E', Flag)
									// Check for successful query
									If Flag EQ 0 then
										// There should only be one work order associated with our unique internal production order number.
										If (DCount(NewWONo, @VM) EQ 1) then
											// Associate new work order with the part number passed in.
											obj_WO_Log('Route', NewWONo:@RM:NewPVNo)
											ErrCode = ''
											If Not(Get_Status(ErrCode)) then
												// Start "migrating" WO_MAT, RDS, etc. entities from previous work order to new work order.
												NewWOStepKey = NewWONo:'*1'
												NewCassNo    = 0
												NewWOMatKeys = ''
												RDSKeys      = Xlate('WO_MAT', WOMatKeys, 'RDS_NO', 'X')
												For each WOMatKey in WOMatKeys using @VM setting vPos
													NewCassNo += 1
													RDSKey     = RDSKeys<0, vPos>
													CassNo     = Field(WOMatKey, '*', 2)
													WOMatRec   = Database_Services('ReadDataRow', 'WO_MAT', WOMatKey)
													If Error_Services('NoError') then
														NewWOMatKey         = NewWONo:'*':NewCassNo
														NewWOMatKeys<0, -1> = NewWOMatKey
														NewWOMatRec         = WOMatRec
														// Modify WO_MAT fields INV_... (6-10, 31, 103, 142), Cust Part No(3), Part No(22)
														// Clear material log starting at 1QA
														InvActions          = WOMatRec<WO_MAT_INV_ACTION$>
														Locate '1QA' in InvActions using @VM setting aPos then
															NewWOMatRec<WO_MAT_INV_WH$>         = Field(WOMatRec<WO_MAT_INV_WH$>, @VM, 1, aPos - 1)
															NewWOMatRec<WO_MAT_INV_LOCATION$>   = Field(WOMatRec<WO_MAT_INV_LOCATION$>, @VM, 1, aPos - 1)
															NewWOMatRec<WO_MAT_INV_ACTION$>     = Field(WOMatRec<WO_MAT_INV_ACTION$>, @VM, 1, aPos - 1)
															NewWOMatRec<WO_MAT_INV_DTM$>        = Field(WOMatRec<WO_MAT_INV_DTM$>, @VM, 1, aPos - 1)
															NewWOMatRec<WO_MAT_INV_USER$>       = Field(WOMatRec<WO_MAT_INV_USER$>, @VM, 1, aPos - 1)
															NewWOMatRec<WO_MAT_INV_TAG$>        = Field(WOMatRec<WO_MAT_INV_TAG$>, @VM, 1, aPos - 1)
															NewWOMatRec<WO_MAT_INV_SCAN_ENTRY$> = Field(WOMatRec<WO_MAT_INV_SCAN_ENTRY$>, @VM, 1, aPos - 1)
															NewWOMatRec<WO_MAT_INV_TOOL_ID$>    = Field(WOMatRec<WO_MAT_INV_TOOL_ID$>, @VM, 1, aPos - 1)
														end
														NewWOMatRec<WO_MAT_CUST_PART_NO$>       = NewEpiPartNo
														NewWOMatRec<WO_MAT_PROD_VER_NO$>        = NewPVNo
														NewWOMatRec<WO_MAT_SHIP_NO$>            = ''              ; // Clear ship number to prevent status of "Shipped" and to allow for re-ship.
														NewWOMatRec<WO_MAT_CONVERTED_MATERIAL$> = True$           ; // This flag is used to override INSP_REQ calculated column in CLEAN_INSP.
														Database_Services('WriteDataRow', 'WO_MAT', NewWOMatKey, NewWOMatRec)
														If Error_Services('NoError') then
															NCRKeys = WOMatRec<WO_MAT_NCR_KEYS$>
															If NCRKeys NE '' then
																For each NCRKey in NCRKeys using @VM setting nPos
																	NCRRec = Database_Services('ReadDataRow', 'NCR', NCRKey)
																	If Error_Services('NoError') then
																		// Modify NCR fields WO_NO(62), WO_MAT_CASS_NO(64), REJ_WAFER_ID(Change first two fields, third will stay the same(Probably MV))(67)
																		NewNCRRec                      = NCRRec
																		NewNCRRec<NCR_WO_NO$>          = NewWONo
																		NewNCRRec<NCR_WO_MAT_CASS_NO$> = NewCassNo
																		WfrIDs                         = NCRRec<NCR_REJ_WAFER_ID$>
																		If WfrIDs NE '' then
																			For each WfrID in WfrIDs using @VM setting wPos
																				NewNCRRec<0, wPos> = NewWONo:'.':NewCassNo:'.':Field(WfrID, '.', 3)
																			Next WfrID
																		end
																		Database_Services('WriteDataRow', 'NCR', NCRKey, NewNCRRec, True$, False$, True$)
																	end else
																		ErrorMsg = Error_Services('GetMessage')
																	end
																Until ErrorMsg NE ''
																Next NCRKey
															end
															If ErrorMsg EQ '' then 
																RDSRec = Database_Services('ReadDataRow', 'RDS', RDSKey)
																If Error_Services('NoError') then
																	// Modify RDS fields Epi Part No(10),  WO(6), Cust No(9), Prod_Spec_Id(114), WO_STEP_KEY(256), CASS_NO(260)
																	NewRDSRec                        = RDSRec
																	NewRDSRec<RDS_WO$>               = NewWONo
																	NewRDSRec<RDS_CUST_NO$>          = NewCustNo
																	NewRDSRec<RDS_PART_NUM$>         = NewEpiPartNo
																	NewRDSRec<RDS_PROD_SPEC_ID$>     = NewPSNo
																	NewRDSRec<RDS_WO_STEP_KEY$>      = NewWOStepKey
																	NewRDSRec<RDS_CASS_NO$>          = NewCassNo
																	NewRDSRec<RDS_SUP_VER_SIG$>      = ''           ; // Clear FQA signature to prevent MFS from blocking write and to re-require FQA
																	NewRDSRec<RDS_SUP_VER_SIG_DATE$> = ''
																	NewRDSRec<RDS_SUP_VER_SIG_TIME$> = ''
																	Database_Services('WriteDataRow', 'RDS', RDSKey, NewRDSRec, True$, False$, True$)
																	If Error_Services('NoError') then
																		// Copy WO_MAT_QA record (no changes necessary)
																		WOMatQARec = Database_Services('ReadDataRow', 'WO_MAT_QA', WOMatKey)
																		Database_Services('WriteDataRow', 'WO_MAT_QA', NewWOMatKey, WOMatQARec)
																		If Error_Services('NoError') then
																			// Modify REACT_RUN fields WO_NO(1), CASS_NO(3), CI_NO(Dont remove, but capture value, may be MV. Field 6)
																			ReactRunRec = Database_Services('ReadDataRow', 'REACT_RUN', RDSKey)
																			If Error_Services('NoError') then
																				NewReactRunRec                     = ReactRunRec
																				NewReactRunRec<REACT_RUN_WO_NO$>   = NewWONo
																				NewReactRunRec<REACT_RUN_CASS_NO$> = NewCassNo
																				Database_Services('WriteDataRow', 'REACT_RUN', RDSKey, NewReactRunRec, True$, False$, True$)
																				If Error_Services('NoError') then
																					CINos = ReactRunRec<REACT_RUN_CI_NO$>
																					If CINos NE '' then
																						For each CINo in CINos using @VM setting cPos
																							// Modify CLEAN_INSP fields WO_NO(1), CASS_NO(3)
																							CIRec = Database_Services('ReadDataRow', 'CLEAN_INSP', CINo)
																							If Error_Services('NoError') then
																								NewCIRec                      = CIRec
																								NewCIRec<CLEAN_INSP_WO_NO$>   = NewWONo
																								NewCIRec<CLEAN_INSP_CASS_NO$> = NewCassNo
																								Database_Services('WriteDataRow', 'CLEAN_INSP', CINo, NewCIRec, True$, False$, True$)
																								If Error_Services('HasError') then
																									ErrorMsg = Error_Services('GetMessage')
																								end
																							end else
																								ErrorMsg = Error_Services('GetMessage')
																							end
																						Until ErrorMsg NE ''
																						Next CINo
																					end
																					RDSLayerKeys = ReactRunRec<REACT_RUN_RDS_LAYER_KEYS$>
																					If RDSLayerKeys NE '' then
																						For each RDSLayerKey in RDSLayerKeys using @VM
																							RDSTestKeys = Xlate('RDS_LAYER', RDSLayerKey, RDS_LAYER_RDS_TEST_KEYS$, 'X')
																							If RDSTestKeys NE '' then
																								For each RDSTestKey in RDSTestKeys using @VM
																									TWUseKeys = Xlate('RDS_TEST', RDSTestKey, RDS_TEST_TW_USE_ID$, 'X')
																									If TWUseKeys NE '' then
																										For each TWUseKey in TWUseKeys using @VM
																											TWUseRec = Database_Services('ReadDataRow', 'TW_USE', TWUseKey)
																											If Error_Services('NoError') then
																												// Modify TW_USE fields WAFER_ID(19)(Can be multivalue) Change first two fields(WO)(CASS_NO) Third will stay the same.
																												NewTWUseRec = TWUseRec
																												TWUseWfrIDs = TWUseRec<TW_USE_WAFER_ID$>
																												If TWUseWfrIDs NE '' then
																													For each TWUseWfrID in TWUseWfrIDs using @VM setting tPos
																														NewTWUseRec<TW_USE_WAFER_ID$, tPos> = NewWONo:'*':NewCassNo:'*':Field(TWUseWfrID, '*', 3)
																													Next TWUseWfrID
																												end
																												Database_Services('WriteDataRow', 'TW_USE', TWUseKey, NewTWUseRec, True$, False$, True$)
																											end else
																												ErrorMsg = Error_Services('GetMessage')
																											end
																										Until ErrorMsg NE '' 
																										Next TWUseKey
																									end
																								Until ErrorMsg NE ''
																								Next RDSTestKey
																							end
																						Until ErrorMsg NE ''
																						Next RDSLayerKey
																					end
																				end else
																					ErrorMsg = Error_Services('GetMessage')
																				end
																			end else
																				ErrorMsg = Error_Services('GetMessage')
																			end
																		end else
																			ErrorMsg = Error_Services('GetMessage')
																		end
																	end else
																		ErrorMsg = Error_Services('GetMessage')
																	end
																end
															end else
																ErrorMsg = Error_Services('GetMessage')
															end
														end else
															ErrorMsg = Error_Services('GetMessage')
														end
													end else
														ErrorMsg = Error_Services('GetMessage')
													end
												Until ErrorMsg NE ''
													// Delete old records
													Database_Services('DeleteDataRow', 'WO_MAT', WOMatKey, True$, False$)
													Database_Services('DeleteDataRow', 'WO_MAT_QA', WOMatKey, True$, False$)
												Next WOMatKey
												If ErrorMsg EQ '' then
													// Update new work order record to contain list of new WO_MAT keys.
													NewWORec = Database_Services('ReadDataRow', 'WO_LOG', NewWONo)
													If Error_Services('NoError') then
														NewWORec<WO_LOG_WO_MAT_KEY$> = NewWOMatKeys
														Database_Services('WriteDataRow', 'WO_LOG', NewWONo, NewWORec, True$, False$, True$)
														If Error_Services('HasError') then
															ErrorMsg = Error_Services('GetMessage')
														end
													end else
														ErrorMsg = Error_Services('GetMessage')
													end
													If ErrorMsg EQ '' then 
														// Reduce source work order quantity by new work order quantity
														WORec                 = Database_Services('ReadDataRow', 'WO_LOG', WONo)
														WORec<WO_LOG_WO_QTY$> = WORec<WO_LOG_WO_QTY$> - NewWOQty
														Database_Services('WriteDataRow', 'WO_LOG', WONo, WORec, True$, False$, True$)
														If Error_Services('HasError') then
															ErrorMsg = Error_Services('GetMessage')
														end
													end
												end
											end else
												ErrorMsg = 'Error in ':Service:' service. Error routing work order ':NewWONo:'. Error message: ':ErrCode:'.'
											end
										end else
											ErrorMsg = 'Error in ':Service:' service. Error retrieving new work order number. Btree.Extract result: "':NewWONo:'".'
										end
									end else
										ErrorMsg = 'Error in ':Service:' service. Error calling Btree.Extract.'
									end
								end else
									ErrorMsg = 'Error in ':Service:' service. Failed to open DICT.WO_LOG table.'
								end
							end else
								ErrorMsg = 'Error in ':Service:' service. obj_WO_Log("SAPCreate") failed. Check SAP_LOG table for details.'
							end
							If ErrorMsg EQ '' then
								// Increment internal production order number
								Database_Services('WriteDataRow', 'APP_INFO', 'CURR_INT_PROD_ORD_NO', NextIntProdOrdNo, True$, False$, False$)
								If Error_Services('HasError') then ErrorMsg = Error_Services('GetMessage')
							end else
								Locked = Database_Services('IsKeyIDLocked', 'APP_INFO', 'CURR_INT_PROD_ORD_NO')
								If Locked then Database_Services('ReleaseKeyIDLock', 'APP_INFO', 'CURR_INT_PROD_ORD_NO')
							end
						end else
							ErrorMsg = 'Error in ':Service:' service. Failed to determine SAP parameters for new work order.'
						end
						
					end else
						ErrorMsg = 'Error in ':Service:' service. Failed to get new internal prod order number.'
					end
				end else
					ErrorMsg = 'Error in ':Service:' service. PROD_VER "':NewPVNo:'" does not exist.'
				end
			end
		end else
			ErrorMsg = 'Error in ':Service:' service. Null CassNos passed into service.'
		end
	end else
		ErrorMsg = 'Error in ':Service:' service. WO_LOG "':WONo:'" does not exist.'
	end
	
	If ErrorMsg EQ '' then
		Response = NewWONo
	end else
		Error_Services('Add', ErrorMsg)
		Response = ''
	end
	
end service


Service ShipConvertedMaterial(ProdOrdNo, CassNos)
	
	ErrorMsg      = ''
	NewShipmentNo = ''
	If ProdOrdNo NE '' then
		If CassNos NE '' then 
			SearchString = 'PROD_ORD_NO':@VM:ProdOrdNo:@FM
			Open 'DICT.WO_LOG' to hDict then
				Flag = ''
				WONo = ''
				Btree.Extract(SearchString, 'WO_LOG', hDict, WONo, 'E', Flag)
				// Check for successful query
				If Flag EQ 0 then
					// There should only be one work order associated with our unique internal production order number.
					If (DCount(WONo, @VM) EQ 1) then
						MockShipData = ''
						For each CassNo in CassNos using @VM setting vPos
							WOMatKey              = WONo:'*':CassNo
							If RowExists('WO_MAT', WOMatKey) then 
								SAPBatchNo            = Xlate('WO_MAT', WOMatKey, 'SAP_BATCH_NO', 'X')
								If SAPBatchNo NE '' then 
									MockShipData<vPos, 1> = ProdOrdNo  ; // SAP Delivery Number - Just use internal IFX generated SAP production order number.
									MockShipData<vPos, 2> = ProdOrdNo  ; // Internal IFX generated SAP production order number.
									MockShipData<vPos, 3> = SAPBatchNo ; // Original SAP batch number associated with cassette.
								end else
									ErrorMsg = 'Error in ':Service:' service. No SAP batch number found for WO_MAT record ':WOMatKey:'.'
								end
							end else
								ErrorMsg = 'Error in ':Service:' service. WO_MAT record ':WOMatKey:' does not exist.' 
							end
						Until ErrorMsg NE ''
						Next CassNo
						If ErrorMsg EQ '' then
							If MockShipData NE '' then
								Success = obj_Shipment('SAPCreate', MockShipData)
								If Not(Success) then
									ErrorMsg = Error_Services('GetMessage')
								end
							end else
								ErrorMsg = 'Error in ':Service:' service. Failed to generate mock ship data file for obj_Shipment("SAPCreate").'
							end
						end
					end else
						ErrorMsg = 'Error in ':Service:' service. Error retrieving work order number. Btree.Extract result: "':WONo:'".'
					end
				end else
					ErrorMsg = 'Error in ':Service:' service. Error calling Btree.Extract.'
				end
			end else
				ErrorMsg = 'Error in ':Service:' service. Failed to open DICT.WO_LOG table.'
			end
		end else
			ErrorMsg = 'Error in ':Service:' service. Null CassNos passed into service.'
		end
	end else
		ErrorMsg = 'Error in ':Service:' service. Null ProdOrdNo passed into service.'
	end
	
	If ErrorMsg EQ '' then
		Response = NewShipmentNo
	end else
		Error_Services('Add', ErrorMsg)
		Response = ''
	end
	
end service


Service MarkMUCassExpired(WoMatKey)
	
	ErrorMsg = ''
	If WoMatKey NE '' then
		If RowExists('MAKEUP_WAFERS', WoMatKey) then
			Open 'MAKEUP_WAFERS' to hMuTable then
				WriteV True$ to hMuTable, WoMatKey, MAKEUP_WAFERS.EXPIRED$ else
					ErrorMsg = 'Error in ':Service:' service. Error writing MAKEUP_WAFERS record "':WoMatKey:'".'
				end
			end else
				ErrorMsg = 'Error in ':Service:' service. Failed to open MAKEUP_WAFERS table.'
			end
		end else
			ErrorMsg = 'Error in ':Service:' service. MAKEUP_WAFERS record "':WoMatKey:'" does not exist!'
		end
	end else
		ErrorMsg = 'Error in ':Service:' service. Null WoMatKey passed in.'
	end
	
	If ErrorMsg NE '' then
		Error_Services('Add', ErrorMsg)
		Response = False$
	end else
		Response = True$
	end
	
end service

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

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

