Function Schedule_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        :   Schedule_Services

    Description :   Handler program for all module related services.

    Notes       :   The generic parameters should contain all the necessary information to process the services. Often
                    this will be information like the data Record and Key ID.

    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)
        06/01/17    dmb     Original programmer. - [IREPIOI-8]
        01/07/18    dmb     Update the AdjustScheduleEvents service to treat 1-day events as previous events so they
                            will get skipped over when future events are adjusted. - [IREPIOI-14]
        05/09/19    dmb     Update AdjustOtherWorkOrderEvents gosub to sort AllWorkOrderEvents using the existing
                            sort order of AllWorkOrderKeys instead of sorting the array directly because the data
                            doesn't allow a predictible sort. - [IREPIOI-70]

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

#pragma precomp SRP_PreCompiler

$Insert APP_INSERTS
$Insert SERVICE_SETUP
$Insert RLIST_EQUATES
$Insert REACTOR_EQUATES
$Insert SCHED_DET_NG_EQUATES
$Insert WO_LOG_EQUATES
$Insert WO_MAT_EQUATES
$Insert WO_SCHEDULE_NG_EQUATES
$Insert COMPANY_EQUATES
$Insert PROD_VER_EQUATES
$Insert PRS_LAYER_EQU
$Insert SCHEDULE_EVENT_SUMMMARY_EQUATES
$Insert EPI_PART_EQUATES
$Insert PROD_SPEC_EQUATES
$Insert SCHED_HIST_EQUATES
$Insert RDS_EQUATES
$Insert WM_IN_EQUATES

Equ new_exist$ To 0 ; * Reduce Mode 0
Equ next_cur$  To 1
Equ add_exist$ To 2

EQU NOTIFICATION_PERIOD$ TO 12

Declare subroutine  Error_Services, Schedule_Services, Memory_Services, RList, Database_Services, Logging_Services
Declare subroutine  Btree.Extract, Set_Status, Reduce, FSMsg, Messaging_Services, obj_Notes, Make.List
Declare subroutine	Mona_Services
Declare function    SRP_Array, Schedule_Services, Memory_Services, Database_Services, SRP_Sort_Array, Datetime
Declare function    Epi_Part_Services, SRP_Math, SRP_Hash, obj_Prod_Spec, Logging_Services, Environment_Services
Declare function    Work_Order_Services, RTI_CreateGUID, Reactor_Services, Schedule_Services, NextKey, SRP_Datetime
Declare function	SRP_Time, Lsl_Users_Services, GetTickCount, obj_WO_Mat

Date            = Oconv(Date(), 'D4/')
LogFileName     = Date[7, 4] : '-' : Date[1, 2] : '-' : Date[4, 2] : ' Scheduler Log.csv'
Headers         = 'Logging DTM' : @FM : 'Service Step' : @FM : 'Service Notes'
ColumnWidths    = 20 : @FM : 30 : @FM : 150

IsProd = Environment_Services('IsProd')
If IsProd EQ True$ then
	MonaResource = 'GRP_OPENINSIGHT_MES_OP_FE_SCHEDULESERVICES'
end else
	MonaResource = 'GRP_OPENINSIGHT_MES_OP_FE_DEV_SCHEDULESERVICES'
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 BLOCK_GROUP = 'HARD_BLOCK', 'SOFT_BLOCK'
Options ACTIONS     = 'ADD', 'STOP', 'CANCEL', 'MODIFY'
Options BOOLEAN     = True$, False$

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

//----------------------------------------------------------------------------------------------------------------------
// GetReactors
//
// Returns an array of reactors that will be used to populate the SRP Schedule control. All reactors will be returned
// other than 0.
//----------------------------------------------------------------------------------------------------------------------
Service GetReactors(ReactorType, SusceptorSize)
	
	ServiceKeyID   := '*' : Reactortype : '*' : SusceptorSize
	Reactors        = Memory_Services('GetValue', ServiceKeyID)
	
	If Reactors EQ '' then
		TableName   = 'REACTOR'
		hReactors   = Database_Services('GetTableHandle', TableName)
		If Error_Services('NoError') then
			Select hReactors
			Done            = False$
			ReactorArray    = ''
			MetaArray       = ''
			Loop
				Readnext ReactorNo ELSE Done = True$
			Until Done
				If ReactorNo GT 0 then
					*                    ReactorRec  = Database_Services('ReadDataRow', TableName, ReactorNo)
					ReactorRec  = Schedule_Services('GetReactorDetails', ReactorNo)
					If Error_Services('NoError') then
						AcceptReactor   = True$ ; // Assume accepted for now.
						Type            = ReactorRec<REACTOR_REACT_TYPE$>
						Size            = ReactorRec<REACTOR_SUSC_POCKET_SIZE$>
						SecondChamber   = ReactorRec<REACTOR_SECOND_CHAMBER$>
						If (ReactorType NE '') AND (Type NE ReactorType) then
							If (ReactorType[1, 3] EQ 'ASM' OR ReactorType EQ 'EPS') AND (Type[1, 3] EQ 'ASM' OR Type EQ 'EPS') else
								// The reactor type for this work order does not match the required reactor type.
								AcceptReactor   = False$
							end
						end
						If AcceptReactor AND SusceptorSize NE '' then
							If Size NE SusceptorSize then
								// The susceptor size for this work order does not match the required susceptor size.
								AcceptReactor   = False$
							end
						end
						If (Type _EQC 'EPP' OR Type _EQC 'EpiPro') AND SecondChamber EQ '' then
							AcceptReactor   = False$
						end
						If AcceptReactor then
							Begin Case
								Case Type _eqc 'ASM+'   ; Type = 'ASM+'
								Case Type _eqc 'EPP'    ; Type = 'EpiPro'
								Case Type _eqc 'EPS'    ; Type = 'ASM'
								Case Type _eqc 'HTR'    ; Type = 'HTR'
								Case Type _eqc 'GAN'    ; Type = 'GaN'
								Case Type _eqc ''	    ; Type = '***'
							End Case
							Assignment  = ReactorRec<REACTOR_REACT_ASSIGNMENT$>
							Location    = ReactorRec<REACTOR_LOCATIONX$>
							Begin Case
								Case Assignment EQ 'M'
									// Dedicated
									Background  = 'Pink'
								Case Assignment EQ 'C'
									// Non-Dedicated
									Background  = 'LightGreen'
								Case Assignment EQ 'G'
									// GaN
									Background  = 'SkyBlue'
								Case Assignment EQ 'O'
									// Out-of-Service
									Background  = 'Silver'
								Case Otherwise$
									// Default to Dedicated
									Background  = 'Pink'
							End Case
							EntityName      = ReactorNo
							If SecondChamber NE '' then EntityName := '/' : SecondChamber
							ReactorArray   := 'ENT' : @VM : 1 : @VM : ReactorNo : @VM : EntityName : @VM : Type : CRLF$ : Size : @VM : 'Black' : @VM : Background : @FM
							MetaArray      := ReactorNo : @VM : Oconv(Assignment, '[REACT_ASSIGN_CONV]') : @VM : Location : @FM
						end
					end
				end
			Repeat
			ReactorArray[-1, 1] = ''
			ReactorArray        = SRP_Array('SortRows', ReactorArray, 'AR3', 'LIST', @FM, @VM)
			MetaArray[-1, 1]    = ''
			MetaArray           = SRP_Array('SortRows', MetaArray, 'AR1', 'LIST', @FM, @VM)
			Reactors            = ReactorArray : @RM : MetaArray
			Memory_Services('SetValue', ServiceKeyID, Reactors)
		end
	end
	
	Response    = Reactors
	
end service


//----------------------------------------------------------------------------------------------------------------------
// GetReactorDetails
//
//----------------------------------------------------------------------------------------------------------------------
Service GetReactorDetails(ReactorNo)
	
	ServiceKeyID   := '*' : ReactorNo
	ReactorDetails  = Memory_Services('GetValue', ServiceKeyID)
	
	If ReactorDetails EQ '' then
		If (ReactorNo NE '') AND (ReactorNo NE 0) then
			TableName       = 'REACTOR'
			ReactorDetails  = Database_Services('ReadDataRow', TableName, ReactorNo)
			If Error_Services('NoError') then
				Memory_Services('SetValue', ServiceKeyID, ReactorDetails)
			end
		end else
			Error_Services('Add', 'ReactorNo argument was missing from the ' : Service : ' service.')
		end
	end
	
	Response    = ReactorDetails
	
end service


//----------------------------------------------------------------------------------------------------------------------
// GetReactorAssignments
//
// Returns an array of key pair "Reactor:Assignment" that will be used to populate the SRP Schedule control. All reactors will be returned
// other than 0.
//----------------------------------------------------------------------------------------------------------------------
Service GetReactorAssignments()
	
	ReactorAssignments    = Memory_Services('GetValue', ServiceKeyID)
	ReactorAssignments    = ''
	
	If ReactorAssignments EQ '' then
		TableName   = 'REACTOR'
		hReactors   = Database_Services('GetTableHandle', TableName)
		If Error_Services('NoError') then
			Select hReactors
			Done = False$
			LOOP
				READNEXT ReactorNo ELSE Done = True$
			UNTIL Done
				If ReactorNo GT 0 then
					ReactorRec  = Database_Services('ReadDataRow', TableName, ReactorNo)
					If Error_Services('NoError') then
						Assignment  = ReactorRec<REACTOR_REACT_ASSIGNMENT$>
						ReactorAssignments   := ReactorNo : @VM : Assignment : @FM
					end
				end
			REPEAT
			ReactorAssignments[-1, 1] = ''
			ReactorAssignments        = SRP_Array('SortRows', ReactorAssignments, 'AR1', 'LIST', @FM, @VM)
			Memory_Services('SetValue', ServiceKeyID, ReactorAssignments)
		end
	end
	
	Response    = ReactorAssignments
	
end service


//----------------------------------------------------------------------------------------------------------------------
// GetTimeBlockList
//
// Returns an array of reactors assignments that will be used to populate the SRP Schedule control. All reactors will be
// returned other than 0.
//----------------------------------------------------------------------------------------------------------------------
Service GetTimeBlockList(StartDate, EndDate)
	
	TimeBlockList       = ''
	ReactorAssignments  = Schedule_Services('GetReactorAssignments')
	For Each ReactorAssignment in ReactorAssignments using @FM
		ReactorNo   = ReactorAssignment<0, 1>
		Assignment  = ReactorAssignment<0, 2>
		For Date = StartDate to EndDate
			StartDTM    = Date : '.0'
			EndDTM      = (Date + 1) : '.0'
			Begin Case
				Case Assignment EQ 'M'
					// Dedicated
					TimeBlockList  := ReactorNo : @VM : ReactorNo : '*' : Date : @VM : StartDTM : @VM : EndDTM : @VM : 'Pink' : @FM
				Case Assignment EQ 'C'
					// Non-Dedicated
					TimeBlockList  := ReactorNo : @VM : ReactorNo : '*' : Date : @VM : StartDTM : @VM : EndDTM : @VM : 'LightGreen' : @FM
				Case Assignment EQ 'G'
					// GaN
					TimeBlockList  := ReactorNo : @VM : ReactorNo : '*' : Date : @VM : StartDTM : @VM : EndDTM : @VM : 'SkyBlue' : @FM
				Case Assignment EQ 'O'
					// Out-of-Service
					TimeBlockList  := ReactorNo : @VM : ReactorNo : '*' : Date : @VM : StartDTM : @VM : EndDTM : @VM : 'Silver' : @FM
				Case Otherwise$
					// Default to Dedicated
					TimeBlockList  := ReactorNo : @VM : ReactorNo : '*' : Date : @VM : StartDTM : @VM : EndDTM : @VM : 'Pink' : @FM
			End Case
		Next Date
	Next ReactorAssignment
	TimeBlockList[-1, 1]    = ''
	
	Response = TimeBlockList
	
end service


//----------------------------------------------------------------------------------------------------------------------
// GetScheduleEvents
//
// Returns an array of schedule details that will be used to populate the SRP Schedule control.
//----------------------------------------------------------------------------------------------------------------------
Service GetScheduleEvents(StartDate, EndDate, ReactorNo, WorkOrderNo, BlockOut)
	
	SRP_Stopwatch('Reset')
	SRP_Stopwatch('Start', Service)
	
	ScheduleEvents = ''
	
	If Not(Num(StartDate)) then StartDate = Iconv(StartDate, 'D')
	If Not(Num(EndDate)) then EndDate = Iconv(EndDate, 'D')
	If BlockOut NE True$ then BlockOut = False$
	
	If (StartDate NE '') then
		SchedDetKeyIDs  = Schedule_Services('GetScheduleDetailKeys', StartDate, EndDate, ReactorNo, WorkOrderNo, BlockOut)
		If SchedDetKeyIDs NE '' then
			SchedDetKeyIDs  = SRP_Array('SortRows', SchedDetKeyIDs, 'AR1' : @FM : 'AR2' : @FM : 'AR3', 'LIST', @FM, '*')
			If Error_Services('NoError') then
				PrevWorkOrderNo     = ''
				Lum                 = 80
				For Each SchedDetKeyID in SchedDetKeyIDs using @FM setting fPos
					SchedDetRec = Database_Services('ReadDataRow', 'SCHED_DET_NG', SchedDetKeyID)
					ReactorNo   = SchedDetRec<SCHED_DET_NG.REACT_NO$>
					WorkOrderNo = SchedDetRec<SCHED_DET_NG.WO_NO$>
					StartDTM    = SchedDetRec<SCHED_DET_NG.START_DTM$>
					StopDTM     = SchedDetRec<SCHED_DET_NG.STOP_DTM$>
					
					ScheduleEventSummary  = Schedule_Services('GetScheduleEventSummary', SchedDetKeyID, False$)
					ScheduleEvents       := ReactorNo : @VM
					ScheduleEvents       := SchedDetKeyID : @VM ; //WorkOrderNo : '*' : ScheduleEventSummary<SES_EPIPARTNO$> : '*' : ReactorNo : '*' : ScheduleDate : @VM
					ScheduleEvents       := ScheduleEventSummary<SES_STARTDTM$> : @VM
					ScheduleEvents       := ScheduleEventSummary<SES_ENDDTM$> : @VM
					ScheduleEvents       := ScheduleEventSummary<SES_BACKCOLOR$> : ' L=' : Lum : @VM
					ScheduleEvents       := ScheduleEventSummary<SES_FORECOLOR$> : @VM
					ScheduleEvents       := ScheduleEventSummary<SES_TITLE$> : @VM
					ScheduleEvents       := ScheduleEventSummary<SES_DESCRIPTION$> : @VM
					ScheduleEvents       := @VM : @VM : @VM : @VM : @VM : @VM : @VM : @VM : @VM : @VM
					ScheduleEvents       := ScheduleEventSummary<SES_HOTLOTFLAG$> : @SVM : ScheduleEventSummary<SES_NOTEFLAG$> : @SVM : ScheduleEventSummary<SES_WOCLOSEDFLAG$> : @SVM : ScheduleEventSummary<SES_CURRENTFLAG$>
					ScheduleEvents       := @FM
				Next SchedDetKeyID
				ScheduleEvents[-1, 1]   = ''
				ScheduleEvents          = SRP_Array('SortRows', ScheduleEvents, 'AR1' : @FM : 'AR3' : @FM : 'AR4' : @FM : 'AL7', 'LIST', @FM, @VM)
			end
		end
	end else
		Error_Services('Add', 'StartDate argument was missing from the ' : Service : ' service.')
	end
	
	Response    = ScheduleEvents
	SRP_Stopwatch('Stop', Service)
	
end service


Service GetScheduleEvent(SchedDetKeyID)
	
	SchedEvent = ''
	If SchedDetKeyID NE '' then
		Lum         = 80
		SchedDetRec = Database_Services('ReadDataRow', 'SCHED_DET_NG', SchedDetKeyID)
		ReactorNo   = SchedDetRec<SCHED_DET_NG.REACT_NO$>
		WorkOrderNo = SchedDetRec<SCHED_DET_NG.WO_NO$>
		StartDTM    = SchedDetRec<SCHED_DET_NG.START_DTM$>
		StopDTM     = SchedDetRec<SCHED_DET_NG.STOP_DTM$>
		
		ScheduleEventSummary  = Schedule_Services('GetScheduleEventSummary', SchedDetKeyID, False$)
		SchedEvent            = ReactorNo : @VM
		SchedEvent           := SchedDetKeyID : @VM 
		SchedEvent           := ScheduleEventSummary<SES_STARTDTM$> : @VM
		SchedEvent           := ScheduleEventSummary<SES_ENDDTM$> : @VM
		SchedEvent           := ScheduleEventSummary<SES_BACKCOLOR$> : ' L=' : Lum : @VM
		SchedEvent           := ScheduleEventSummary<SES_FORECOLOR$> : @VM
		SchedEvent           := ScheduleEventSummary<SES_TITLE$> : @VM
		SchedEvent           := ScheduleEventSummary<SES_DESCRIPTION$> : @VM
		SchedEvent           := @VM : @VM : @VM : @VM : @VM : @VM : @VM : @VM : @VM : @VM
		SchedEvent           := ScheduleEventSummary<SES_HOTLOTFLAG$> : @SVM : ScheduleEventSummary<SES_NOTEFLAG$> : @SVM : ScheduleEventSummary<SES_WOCLOSEDFLAG$> : @SVM : ScheduleEventSummary<SES_CURRENTFLAG$>
	end
	Response = SchedEvent
	
end service


//----------------------------------------------------------------------------------------------------------------------
// GetScheduleEventSummary
//
// Returns an @FM array of event summary data related to the indicated reactor and work order.
//----------------------------------------------------------------------------------------------------------------------
Service GetScheduleEventSummary(SchedDetKeyID, IncludeWaferDetails)
	
	If IncludeWaferDetails NE True$ then IncludeWaferDetails = False$
	ScheduleEventSummary    = ''
	If SchedDetKeyID NE '' then
		SchedDetRec    = Database_Services('ReadDataRow', 'SCHED_DET_NG', SchedDetKeyID)
		ReactorNo      = SchedDetRec<SCHED_DET_NG.REACT_NO$>
		WorkOrderNo    = SchedDetRec<SCHED_DET_NG.WO_NO$>
		StartDTM       = SchedDetRec<SCHED_DET_NG.START_DTM$>
		StopDTM        = SchedDetRec<SCHED_DET_NG.STOP_DTM$>
		Desc           = SchedDetRec<SCHED_DET_NG.DESC$>
		Modified       = SchedDetRec<SCHED_DET_NG.MODIFIED$>
		BlockOut       = SchedDetRec<SCHED_DET_NG.BLOCK_OUT$>
		BlockOutType   = SchedDetRec<SCHED_DET_NG.BLOCK_OUT_TYPE$>
		BackColor      = SchedDetRec<SCHED_DET_NG.BACKCOLOR$>
		ForeColor      = SchedDetRec<SCHED_DET_NG.FORECOLOR$>
		EventWfrQty    = SchedDetRec<SCHED_DET_NG.EVENT_TOTAL_WFRS$>
		
		ReactorDetails = Schedule_Services('GetReactorDetails', ReactorNo)
		Type           = ReactorDetails<REACTOR_REACT_TYPE$>
		Size           = ReactorDetails<REACTOR_SUSC_POCKET_SIZE$>
		WOLogRow       = Memory_Services('GetValue', 'WOLogRow' : '*' : ReactorNo : '*' : WorkOrderNo, True$, 5)
		If WOLogRow EQ '' then
			WOLogRow        = Work_Order_Services('GetWorkOrderLogDetail', WorkOrderNo)
			Memory_Services('SetValue', 'WOLogRow' : '*' : ReactorNo : '*' : WorkOrderNo, WOLogRow)
		end
		CustNo          = WOLogRow<WO_LOG_CUST_NO$>
		EpiPartNo       = WOLogRow<WO_LOG_EPI_PART_NO$>
		ProdVerNo       = WOLogRow<WO_LOG_PROD_VER_NO$>
		
		TotalWafers  = SchedDetRec<SCHED_DET_NG.EVENT_TOTAL_WFRS$>
		WOClosedFlag = SchedDetRec<SCHED_DET_NG.EVENT_COMP$>
		HotLotFlag   = WOLogRow<WO_LOG_HOT_FLAG$>
		If Memory_Services('IsValueExpired', 'CompanyRow' : '*' : ReactorNo : '*' : WorkOrderNo, 60, True$) then
			CompanyRow  = Database_Services('ReadDataRow', 'COMPANY', CustNo)
			Memory_Services('SetValue', 'CompanyRow' : '*' : ReactorNo : '*' : WorkOrderNo, CompanyRow)
		end else
			CompanyRow      = Memory_Services('GetValue', 'CompanyRow' : '*' : ReactorNo : '*' : WorkOrderNo)
		end
		CustName        = CompanyRow<COMPANY_ABBREV$>
		Begin Case
			Case CustName _EQC 'International Rectifier'    ; CustNameShort = 'IR'
			Case CustName _EQC 'IRNewport'                  ; CustNameShort = 'Newport'
			Case CustName _EQC 'IFX (Kulim)'                ; CustNameShort = 'Kulim'
			Case CustName _EQC 'IFX Austria AG'             ; CustNameShort = 'Austria'
			Case CustName _EQC 'Tower Semiconductor'        ; CustNameShort = 'Tower'
			Case Otherwise$                                 ; CustNameShort = CustName
		End Case
		TableName    = 'SCHED_DET_NG'
		NoteFlag = ''
		If BlockOut then
			Title   = 'Block Out' : ' (' : BlockOutType : ')'
		end else
			Title   = WorkOrderNo : ' (' : CustNameShort : ' ' : EpiPartNo : ')'
		end
		HardBlock = ( (BlockOutType EQ 'Out of Service (OOS)') or (BlockOutType EQ 'Down No Material (DNM)') or (BlockOutType EQ 'Factory Down Time') )
		Begin Case
			Case BackColor NE ''
				// Set in the schedule detail record. Use current color.
			Case WOClosedFlag
				BackColor = 'LightGray'
			Case HotLotFlag
				BackColor = 'LightCoral'				
			Case HardBlock
				BackColor = 'LightGoldenRodYellow'
			Case BlockOut
				BackColor = 'Plum'
			Case Otherwise$
				BackColor = 'LightSteelBlue'
		End Case
		Begin Case
			Case ForeColor NE ''
				// Set in the schedule detail record. Use current color.
			Case NoteFlag AND HotLotFlag
				ForeColor   = 'Yellow'
			Case NoteFlag AND BlockOut
				ForeColor   = 'Indigo'
			Case NoteFlag
				ForeColor   = 'Red'
			Case Otherwise$
				ForeColor   = 'Black'
		End Case
		
		If IncludeWaferDetails EQ True$ then
			Remaining = Schedule_Services('GetEventRemainingWfrs', SchedDetKeyID)
			If TotalWafers GT 0 then
				Complete    = (TotalWafers - Remaining) / TotalWafers * 100
			end else
				Complete    = 0
			end
			TotalDays = SRP_Math('ROUND', StopDTM - StartDTM, 1)
			ProdVerRow  = Memory_Services('GetValue', 'ProdVerRow' : '*' : ReactorNo : '*' : WorkOrderNo, True$, 5)
			If ProdVerRow EQ '' then
				ProdVerRow  = Database_Services('ReadDataRow', 'PROD_VER', ProdVerNo)
				Memory_Services('SetValue', 'ProdVerRow' : '*' : ReactorNo : '*' : WorkOrderNo, ProdVerRow)
			end
			PSN         = ProdVerRow<PROD_VER_PROC_STEP_PSN$>
			LayerSpecs  = obj_Prod_Spec('GetLayerProp',PSN:@RM:@RM:1)	;* Returns specs for all layers in internal format
			LayerSpec 	= FIELD(LayerSpecs,@RM,1)				;* Take the first Layer
			LayerSet	= FIELD(LayerSpec,@FM,1)				;* Not used here but shown for clarity
			LayerSpec	= FIELD(LayerSpec,@FM,2,99)				;* LayerSpec without the LayerSet 
			Recipe      = LayerSpec<PRS_LAYER_RECIPE$>
		end else
			Remaining   = ''
			TotalWafers = ''
			Complete    = ''
			TotalDays   = ''
			PSN         = ''
			Recipe      = ''
		end
		StartDate             = Field(StartDTM, '.', 1)
		EndDate               = Field(StopDTM, '.', 1)
		ScheduleEventSummary := Type : @FM
		ScheduleEventSummary := Size : @FM
		ScheduleEventSummary := CustName : @FM
		ScheduleEventSummary := CustNameShort : @FM
		ScheduleEventSummary := EpiPartNo : @FM
		ScheduleEventSummary := Oconv(StartDate, 'D4/') : @FM
		ScheduleEventSummary := Oconv(EndDate, 'D4/') : @FM
		ScheduleEventSummary := TotalDays : @FM
		ScheduleEventSummary := EventWfrQty : @FM
		ScheduleEventSummary := Remaining : @FM
		ScheduleEventSummary := Oconv(Complete, 'MD0[ %]S') : @FM
		ScheduleEventSummary := WOClosedFlag : @FM
		ScheduleEventSummary := HotLotFlag : @FM
		ScheduleEventSummary := '' : @FM ; //CurrentFlag : @FM
		ScheduleEventSummary := NoteFlag : @FM
		ScheduleEventSummary := StartDTM : @FM
		ScheduleEventSummary := StopDTM : @FM
		ScheduleEventSummary := Title : @FM
		ScheduleEventSummary := Desc : @FM
		ScheduleEventSummary := BackColor : @FM
		ScheduleEventSummary := ForeColor : @FM
		ScheduleEventSummary := PSN : @FM
		ScheduleEventSummary := Recipe : @FM
		ScheduleEventSummary := '' : @FM  ; // Day Length Code ?
		ScheduleEventSummary := BlockOut : @FM
		ScheduleEventSummary := BlockOutType
		
	end else
		Error_Services('Add', 'ReactorNo or WorkOrderNo argument was missing from the ' : Service : ' service.')
	end
	
	Response    = ScheduleEventSummary
	
end service


//----------------------------------------------------------------------------------------------------------------------
// GetScheduleDetailKeys
//
// Returns an array of schedule detail KeyIDs based on the arguments provided.
//----------------------------------------------------------------------------------------------------------------------
Service GetScheduleDetailKeys(StartDate, EndDate, ReactorNo, WorkOrderNo, BlockOut, InProcess=BOOLEAN)
	
	ScheduleDetailKeys  = ''
	
	If Not(Num(StartDate)) then StartDate = Iconv(StartDate, 'DT')
	If Not(Num(EndDate)) then EndDate = Iconv(EndDate, 'DT')
	If BlockOut NE True$ then BlockOut = False$
	
	If ( (ReactorNo NE '') or (StartDate NE '') ) then
		CursorVar            = ''
		SortList             = 'REACT_NO' : @FM : 'START_DTM'
		Flag                 = ''
		Done                 = False$
		ReduceScript         = ''
		If ReactorNo NE '' then ReduceScript := 'WITH {REACT_NO} EQ "':ReactorNo:'" AND '
		If StartDate NE '' then ReduceScript := "WITH {START_DTM} GT ":Quote(OConv(StartDate, 'DT/^3H')):' AND '
		// Additional filters can be added here
		ReduceScript[-1, -5] = ''
		GoSub ClearCursors
		Reduce(ReduceScript, SortList, 1, 'SCHED_DET_NG', CursorVar, Flag)
		If Flag then
			Select 'SCHED_DET_NG' By SortList using CursorVar then
				Open 'SCHED_DET_NG' to hTable then
					Loop
						ReadNext Key using CursorVar by AT Else Done = True$
					Until Done
						Locate Key in ScheduleDetailKeys using @FM setting fPos else
							// This ensures the list is unique. Duplicates can be found by the Select statement
							// if the index on REACT_NO or START_DTM is compromised.
							ScheduleDetailKeys<-1> = Key
						end
					Repeat
				end
			end
		end
		
		GoSub ClearCursors
		
		If InProcess then
			// Perform a second select to ensure currently running events get selected.
			Flag                 = ''
			Done                 = False$
			ReduceScript         = 'WITH {UNPROCESSED_CASS} NE ""'
			// Additional filters can be added here
			GoSub ClearCursors
			Reduce(ReduceScript, SortList, 1, 'SCHED_DET_NG', CursorVar, Flag)
			If Flag then
				Select 'SCHED_DET_NG' By SortList using CursorVar then
					Open 'SCHED_DET_NG' to hTable then
						Loop
							ReadNext Key using CursorVar by AT Else Done = True$
						Until Done
							Locate Key in ScheduleDetailKeys using @FM setting fPos else
								// This ensures the list is unique. Duplicates can be found by the Select statement
								// if the index on REACT_NO or START_DTM is compromised.
								ScheduleDetailKeys<-1> = Key
							end
						Repeat
					end
				end
			end
		end
		
	end else
		Error_Services('Add', 'StartDate argument was missing from the ' : Service : ' service.')
	end
	
	Response    = ScheduleDetailKeys
	
end service


Service CancelScheduleEvent(SchedDetKeyID, ShiftEvents)
	
	RemSchedDetKeys = ''
	AddSchedDetKeys = ''
	If SchedDetKeyID NE '' then
		
		SchedDetRec = Database_Services('ReadDataRow', 'SCHED_DET_NG', SchedDetKeyID)
		ReactNo     = SchedDetRec<SCHED_DET_NG.REACT_NO$>
		
		ReactRec         = Database_Services('ReadDataRow', 'REACTOR', ReactNo)
		ReactSchedEvents = ReactRec<REACTOR_SCHED_EVENTS$>
		Locate SchedDetKeyID in ReactSchedEvents using @VM setting vPos then
			PrevSchedKeyID                  = ReactSchedEvents<0, vPos - 1>
			If PrevSchedKeyID NE '' then
				// Mark this event as not complete.
				PrevSchedDetRec                           = Database_Services('ReadDataRow', 'SCHED_DET_NG', PrevSchedKeyID)
				PrevSchedDetRec<SCHED_DET_NG.EVENT_COMP$> = 0
				Database_Services('WriteDataRow', 'SCHED_DET_NG', PrevSchedKeyID, PrevSchedDetRec, True$, False$, True$)
			end
		end
		
		// Delete canceled event record
		Database_Services('DeleteDataRow', 'SCHED_DET_NG', SchedDetKeyID)
	end
	
	// Record schedule change in SCHED_HIST table
	Schedule_Services('AddSchedHist', SchedDetKeyID, ReactNo, 'CANCEL', @User4)
	
	Response       = RemSchedDetKeys 
	
end service


Service GetStartCassNo(SchedDetKey)
	
	RDSNo  = ''
	CassNo = ''
	If SchedDetKey NE '' then
		SchedDetRec   = Schedule_Services('GetScheduleDetail', SchedDetKey)
		WONo          = SchedDetRec<SCHED_DET_NG.WO_NO$>
		ReactorNo     = SchedDetRec<SCHED_DET_NG.REACT_NO$>
		EventStartDTM = SchedDetRec<SCHED_DET_NG.START_DTM$>
		StartDate     = EventStartDTM[1, 'F.']
		StartTime     = (EventStartDTM[-1, 'B.'] * 86400)
		StartTime     = StartTime[1, 5]
		// Find first cassette with START_DT and START_TIME after event START_DTM
		Query = 'SELECT RDS WITH WO EQ ':WONo:' AND WITH REACTOR EQ ':ReactorNo:' BY DATE_IN BY TIME_IN'
		Set_Status(0)
		GoSub ClearCursors
		Rlist(Query, Target_ActiveList$, '', '', '')
		If @RecCount then
			Done = False$
			EOF  = False$
			Loop
				ReadNext RDSNo else EOF = True$
			Until EOF EQ True$
				DTMIn = Xlate('RDS', RDSNo, 'DATETIME_IN', 'X')
				If DTMIN GE EventStartDTM then
					Done = True$
				end
			Until Done EQ True$
			Repeat
		end
		GoSub ClearCursors
		errCode            = ''
		If Get_Status(errCode) then
			ErrorMsg = 'Error in service ':Service:' module. Error code':errCode
			Error_Services('Add', ErrorMsg)       
		end
	end
	If RDSNo NE '' then CassNo = Xlate('RDS', RDSNo, 'CASS_NO', 'X')
	Response = CassNo
	
end service


Service GetEventLastCassNo(SchedDetKey)
	
	RDSNo  = ''
	CassNo = ''
	If SchedDetKey NE '' then
		SchedDetRec    = Schedule_Services('GetScheduleDetail', SchedDetKey)
		ReactorNo      = SchedDetRec<SCHED_DET_NG.REACT_NO$>
		EventStopDTM   = SchedDetRec<SCHED_DET_NG.STOP_DTM$>
		WONo           = SchedDetRec<SCHED_DET_NG.WO_NO$>
		EventStartDTM  = SchedDetRec<SCHED_DET_NG.START_DTM$>
		StartDate      = EventStartDTM[1, 'F.']
		StartTime      = (EventStartDTM[-1, 'B.'] * 86400)
		StartTime      = StartTime[1, 5]
		StopDate       = EventStopDTM[1, 'F.']
		StopTime       = (EventStopDTM[-1, 'B.'] * 86400)
		StopTime       = StopTime[1, 5]
		// Find first cassette with START_DT and START_TIME after event START_DTM
		Query          = 'SELECT RDS WITH WO EQ ':WONo:' AND WITH REACTOR EQ ':ReactorNo:' BY-DSND DATE_OUT BY-DSND TIME_OUT'
		Set_Status(0)
		GoSub ClearCursors
		Rlist(Query, Target_ActiveList$, '', '', '')
		If @RecCount then
			Done = False$
			EOF  = False$
			Loop
				ReadNext RDSNo else EOF = True$
			Until EOF EQ True$
				DTMOut = Xlate('RDS', RDSNo, 'DATETIME_OUT', 'X')
				If DTMOut LE EventStopDTM then
					Done = True$
				end
			Until Done EQ True$
			Repeat
		end
		GoSub ClearCursors
		errCode            = ''
		If Get_Status(errCode) then
			ErrorMsg = 'Error in service ':Service:' module. Error code':errCode
			Error_Services('Add', ErrorMsg)       
		end
	end
	If RDSNo NE '' then CassNo = Xlate('RDS', RDSNo, 'CASS_NO', 'X')
	Response = CassNo
	
end service


Service GetEventRemainingWfrs(SchedDetKey)
	
	WfrsRemaining = 0
	If SchedDetKey NE '' then
		SchedDetRec             = Schedule_Services('GetScheduleDetail', SchedDetKey)
		WONo                    = SchedDetRec<SCHED_DET_NG.WO_NO$>
		ReactType               = Xlate('WO_LOG', WONo, 'REACT_TYPE', 'X')
		SchedQty                = SchedDetRec<SCHED_DET_NG.EVENT_TOTAL_WFRS$>
		UnprocessedCassettes    = SchedDetRec<SCHED_DET_NG.UNPROCESSED_CASS$>
		NumUnprocessedCassettes = DCount(UnprocessedCassettes, @VM)
		If ReactType NE 'EPP' then
			WfrsRemaining = 25 * NumUnprocessedCassettes
		end else
			If UnprocessedCassettes NE '' then
				QueryEndDtm     = SchedDetRec<SCHED_DET_NG.START_DTM$>
				PrevSchedWfrQty = Schedule_Services('GetScheduledWfrQty', WONo, QueryEndDtm)
				StartSlot       = Mod(PrevSchedWfrQty, 25) + 1
				StartCass       = SRP_Math('CEILING', ( (PrevSchedWfrQty + 1) / 25))
				If StartCass EQ 0 then StartCass = 1
				LastUnprocCass  = UnprocessedCassettes<0, NumUnprocessedCassettes>
				TotalQty        = PrevSchedWfrQty + SchedQty
				StopSlot        = Mod(TotalQty, 25)
				If StopSlot EQ 0 then StopSlot = 25
				For each UnprocCass in UnprocessedCassettes using @VM setting vPos
					WMIKey  = WONo:'*1*':UnprocCass
					If RowExists('WM_IN', WMIKey) then
						RDSKeys    = Xlate('WM_IN', WMIKey, 'RDS_NO', 'X')
						UnloadDtms = Xlate('RDS', RDSKeys, 'DATETIME_OUT', 'X')
						If UnprocCass EQ StartCass then
							StartIndex = StartSlot
						end else
							StartIndex = 1
						end
						For SlotIndex = StartIndex to 25
							If UnloadDtms<0, SlotIndex> EQ '' then WfrsRemaining += 1
						Until ( (UnprocCass EQ LastUnprocCass) and (SlotIndex EQ StopSlot) )
						Next SlotIndex
					end else
						WfrsRemaining += 25
					end
				Next UnprocCass
			end
		end
		Response = WfrsRemaining
	end
	If WfrsRemaining GT 0 then
		Response = WfrsRemaining
	end else
		Response = 0
		Error_Services('Add', 'Error in ':Service:' service. Negative wafers remaining value returned!')
	end
	
end service


Service GetEventCompStatus(SchedDetKey)
	
	If SchedDetKey NE '' then
		SchedDetRec        = Schedule_Services('GetScheduleDetail', SchedDetKey)
		SchedQty           = SchedDetRec<SCHED_DET_NG.EVENT_TOTAL_WFRS$>      
		NumUnprocWfrs      = Schedule_Services('GetEventRemainingWfrs', SchedDetKey)
		NumWfrsProcessed = SchedQty - NumUnprocWfrs
		If ( (SchedQty NE '') and (SchedQty GT 0) ) then
			PercentComplete   = (NumWfrsProcessed / SchedQty)
			Response          = PercentComplete
		end
	end
	
end service


Service GetCurrentEvent(ReactNo)
	StartTick = GetTickCount()
    MetricName = 'GetCurrentEvent'
	
	SchedDetKey = ''
	ErrorMsg = ''
	If ReactNo NE '' then
		CurrDTM  = Datetime()
		Query    = 'SELECT SCHED_DET_NG WITH REACT_NO EQ "':ReactNo:'" AND WITH EVENT_COMP NE 1 BY START_DTM'
		Set_Status(0)
		GoSub ClearCursors
		Rlist(Query, TARGET_ACTIVELIST$, '', '', '')
		errCode = ''
		If Not(Get_Status(errCode)) then
			ReadNext SchedDetKey else EOF = True$
			Response = SchedDetKey
		end else
			ErrorMsg = 'Error in service ':Service:' module. Error code ':errCode 
		end
		GoSub ClearCursors
	end
	
	EndTick = GetTickCount()
    Mona_Services('QueueLatencyAndCountMetrics', MonaResource, MetricName, StartTick, EndTick)
    
    If ErrorMsg NE '' then Error_Services('Add', ErrorMsg)
end service

Service CurrentEventIsBlock(ReactNo)
	StartTick = GetTickCount()
    MetricName = 'CurrentEventIsBlock'
	
	ErrorMsg = ''
	If ReactNo NE '' then
		CurrentSchedDetKey = Schedule_Services('GetCurrentEvent', ReactNo)
				
		If Error_Services('NoError') then
			CurrentSchedDetRecord = Database_Services('ReadDataRow', 'SCHED_DET_NG', CurrentSchedDetKey)
			
			CurrentEventIsABlock = CurrentSchedDetRecord<SCHED_DET_NG.BLOCK_OUT$>
			If CurrentEventIsABlock EQ '' then
				CurrentEventIsABlock = False$
			end
			
			Response = CurrentEventIsABlock
		end else
			Error = Error_Services('GetMessage')
			ErrorMsg = 'Error in service ':Service:' module. ':Error
		end
	end else
		ErrorMsg = 'Error in service ':Service:' module. Invalid reactor number.'
	end
	
	EndTick = GetTickCount()
    Mona_Services('QueueLatencyAndCountMetrics', MonaResource, MetricName, StartTick, EndTick)
    
    If ErrorMsg NE '' then Error_Services('Add', ErrorMsg)
end service

Service GetNextEvent(ReactNo)
	StartTick = GetTickCount()
    MetricName = 'GetNextEvent'
	
	SchedDetKey = ''
	ErrorMsg = ''
	If ReactNo NE '' then
		CurrDTM  = Datetime()
		Query    = 'SELECT SCHED_DET_NG WITH REACT_NO EQ "':ReactNo:'" AND WITH EVENT_COMP NE 1 BY START_DTM'
		Set_Status(0)
		GoSub ClearCursors
		Rlist(Query, TARGET_ACTIVELIST$, '', '', '')
		errCode = ''
		If Not(Get_Status(errCode)) then
			EOF = False$
			ReadNext SchedDetKey else EOF = True$
			If EOF EQ True$ then
				ErrorMsg = 'Error in service ':Service:' module. No incomplete events in schedule'
			end else
				ReadNext SchedDetKey else EOF = True$
				If EOF EQ True$ then
					ErrorMsg = 'Error in service ':Service:' module. No incomplete events in schedule after current event'
				end else
					Response = SchedDetKey
				end
			end	
		end else
			ErrorMsg = 'Error in service ':Service:' module. Error code ':errCode  
		end
		GoSub ClearCursors
	end
	
	EndTick = GetTickCount()
    Mona_Services('QueueLatencyAndCountMetrics', MonaResource, MetricName, StartTick, EndTick)
    
    If ErrorMsg NE '' then Error_Services('Add', ErrorMsg)
end service

Service GetEngagedEvent(ReactNo)
	
	SchedDetKey = ''
	If ReactNo NE '' then
		CurrDTM  = Datetime()
		Query    = 'SELECT SCHED_DET_NG WITH REACT_NO EQ "':ReactNo:'" AND WITH UNPROCESSED_CASS NE "" AND WITH EVENT_COMP NE 1 BY START_DTM'
		Set_Status(0)
		GoSub ClearCursors
		Rlist(Query, TARGET_ACTIVELIST$, '', '', '')
		errCode = ''
		If Not(Get_Status(errCode)) then
			ReadNext SchedDetKey else EOF = True$
			Response = SchedDetKey
		end else
			ErrorMsg = 'Error in service ':Service:' module. Error code ':errCode
			Error_Services('Add', ErrorMsg)  
		end
		GoSub ClearCursors
	end
	
end service


Service GetLastEngagedEvent(ReactNo)
	
	SchedDetKey = ''
	If ReactNo NE '' then
		CurrDTM  = Datetime()
		Query    = 'SELECT SCHED_DET_NG WITH REACT_NO EQ "':ReactNo:'" AND WITH START_DTM LT "':CurrDTM:'" '
		Query   := 'BY-DSND START_DTM'
		Set_Status(0)
		GoSub ClearCursors
		Rlist(Query, Target_ActiveList$, '', '', '')
		ReadNext SchedDetKey else EOF = True$
		GoSub ClearCursors
		errCode            = ''
		If Get_Status(errCode) then
			ErrorMsg = 'Error in service ':Service:' module. Error code':errCode
			Error_Services('Add', ErrorMsg)       
		end else
			Response = SchedDetKey
		end
	end
	
end service

Service NextEventIsSamePsn(ReactNo)
	StartTick = GetTickCount()
    MetricName = 'NextEventIsSamePsn'
	
	Response = False$
	ErrorMsg = ''
	If ReactNo NE '' then
		CurrentSchedDetKey = Schedule_Services('GetCurrentEvent', ReactNo)
		
		If Error_Services('NoError') then
			CurrentEventIsABlock = Schedule_Services('CurrentEventIsBlock', ReactNo)
			If CurrentEventIsABlock EQ False$ then
				CurrentSchedDetRecord = Database_Services('ReadDataRow', 'SCHED_DET_NG', CurrentSchedDetKey)
			
				CurrentEventWoNo = CurrentSchedDetRecord<SCHED_DET_NG.WO_NO$>
				CurrentEventWoMatRecord = Database_Services('ReadDataRow', 'WO_MAT', CurrentEventWoNo:'*1')
				
				CurrentEventRdsNo = CurrentEventWoMatRecord<WO_MAT_RDS_NO$>
				CurrentEventRdsRecord = Database_Services('ReadDataRow', 'RDS', CurrentEventRdsNo)
				
				CurrentEventPsn = CurrentEventRdsRecord<RDS_PROD_SPEC_ID$>
				
				NextSchedDetKey = Schedule_Services('GetNextEvent', ReactNo)
				
				If Error_Services('NoError') then
					NextEventIsABlock = Schedule_Services('NextEventIsBlock', ReactNo)
					If NextEventIsABlock EQ False$ then
						NextSchedDetRecord = Database_Services('ReadDataRow', 'SCHED_DET_NG', NextSchedDetKey)
					
						NextEventWoNo = NextSchedDetRecord<SCHED_DET_NG.WO_NO$>
						NextEventWoMatRecord = Database_Services('ReadDataRow', 'WO_MAT', NextEventWoNo:'*1')
						
						NextEventRdsNo = NextEventWoMatRecord<WO_MAT_RDS_NO$>
						NextEventRdsRecord = Database_Services('ReadDataRow', 'RDS', NextEventRdsNo)
						
						NextEventPsn = NextEventRdsRecord<RDS_PROD_SPEC_ID$>
						
						PsnsMatch = CurrentEventPsn EQ NextEventPsn
						
						Response = PsnsMatch
					end
				end
			end
		end		
	end else
		ErrorMsg = 'Error in service ':Service:' module. Invalid reactor number.'
	end
	
	EndTick = GetTickCount()
    Mona_Services('QueueLatencyAndCountMetrics', MonaResource, MetricName, StartTick, EndTick)
    
    If ErrorMsg NE '' then Error_Services('Add', ErrorMsg)
end service

Service NextEventIsBlock(ReactNo)
	StartTick = GetTickCount()
    MetricName = 'NextEventIsBlock'
	
	ErrorMsg = ''
	If ReactNo NE '' then
		NextSchedDetKey = Schedule_Services('GetNextEvent', ReactNo)
				
		If Error_Services('NoError') then
			NextSchedDetRecord = Database_Services('ReadDataRow', 'SCHED_DET_NG', NextSchedDetKey)
			
			NextEventIsABlock = NextSchedDetRecord<SCHED_DET_NG.BLOCK_OUT$>
			If NextEventIsABlock EQ '' then
				NextEventIsABlock = False$
			end
			
			Response = NextEventIsABlock
		end else
			Error = Error_Services('GetMessage')
			ErrorMsg = 'Error in service ':Service:' module. ':Error
		end
	end else
		ErrorMsg = 'Error in service ':Service:' module. Invalid reactor number.'
	end
	
	EndTick = GetTickCount()
    Mona_Services('QueueLatencyAndCountMetrics', MonaResource, MetricName, StartTick, EndTick)
    
    If ErrorMsg NE '' then Error_Services('Add', ErrorMsg)
end service

//----------------------------------------------------------------------------------------------------------------------
// AdjustScheduleEvents
//
// Adjusts the schedule detail rows for a given reactor. 
//----------------------------------------------------------------------------------------------------------------------
Service AdjustScheduleEvents(ReactNo)

	HaveLock  = False$
	hSysLists = Database_Services('GetTableHandle', 'SYSLISTS')
	Lock hSysLists, ServiceKeyID:'*':ReactNo then
		HaveLock       = True$
		CurrDTM        = Datetime()
			
		// Log a line break to separate service calls
		LogData    = ''
		LogData<1> = ''
		LogData<2> = ''
		LogData<3> = ''
		Schedule_Services('LogActivity', ReactNo, LogData)                   
		
		LogData    = ''
		LogData<1> = OConv(Datetime(), 'DT2/^H')
		LogData<2> = '1'
		LogData<3> = 'Begin Adjusting Reactor ':ReactNo
		Schedule_Services('LogActivity', ReactNo, LogData)
		
		OrigReactNo        = ReactNo
		Done               = False$
		
		// Mark all hard blocks with end datetimes before the current date time as complete.
		
		LogData    = ''
		LogData<1> = OConv(Datetime(), 'DT2/^H')
		LogData<2> = '2'
		LogData<3> = 'Checking for hard block events to close.'
		Schedule_Services('LogActivity', ReactNo, LogData)                
		
		EOF      = False$
		Query    = 'SELECT SCHED_DET_NG WITH REACT_NO EQ ':Quote(ReactNo):' AND WITH BLOCK_OUT EQ ':Quote(True$)
		Query   := ' AND WITH STOP_DTM LT ':Quote(CurrDtm):' AND WITH EVENT_COMP NE 1 AND WITH HARD_BLOCK EQ 1'
		GoSub ClearCursors
		Set_Status(0)
		RList(Query, TARGET_ACTIVELIST$, '', '', '')
		If Not(Get_Status(ErrCode)) then
			LoopIndex = 0
			Loop
				ReadNext OldBlockOutKey else EOF = True$
			Until EOF
				LoopIndex      += 1
				OldBlockOutRec  = Database_Services('ReadDataRow', 'SCHED_DET_NG', OldBlockOutKey)
				If Error_Services('NoError') then
					
					LogData    = ''
					LogData<1> = OConv(Datetime(), 'DT2/^H')
					LogData<2> = '2.':LoopIndex
					LogData<3> = 'Marking previous hard block event complete: ':OldBlockOutKey
					Schedule_Services('LogActivity', ReactNo, LogData)
					
					OldBlockOutRec<SCHED_DET_NG.EVENT_COMP$> = True$
					Database_Services('WriteDataRow', 'SCHED_DET_NG', OldBlockOutKey, OldBlockOutRec, True$, False$, True$)
				end
			Repeat
		end
		
		// Find the current event and start adjusting from there.
		CurrEventKey = Schedule_Services('GetCurrentEvent', ReactNo)
		If CurrEventKey EQ '' then
			CurrEventKey = Schedule_Services('GetLastEngagedEvent', ReactNo)
		end
		
		LogData    = ''
		LogData<1> = OConv(Datetime(), 'DT2/^H')
		LogData<2> = '3.1'
		LogData<3> = 'Located starting event to adjust from: ':CurrEventKey
		Schedule_Services('LogActivity', ReactNo, LogData)
		
		ReactEventList     = Xlate('REACTOR', ReactNo, 'SCHED_EVENTS', 'X')
		OrigReactEventList = ReactEventList
		NewReactEventList  = ''
		If ReactEventList NE '' then
			NumEvents         = DCount(ReactEventList, @VM)
			NonHBList         = ''
			HBList            = ''
			TruncatePos       = ''
			// Build list of non-hard block events and a list of hard block events.
			Locate CurrEventKey in ReactEventList using @VM setting vPos then
				TruncatePos = vPos
				For EventKeyIndex = vPos to NumEvents
					EventKey  = ReactEventList<0, EventKeyIndex>
					HardBlock = Xlate('SCHED_DET_NG', EventKey, 'HARD_BLOCK', 'X')
					If HardBlock EQ True$ then
						HBList<0, -1> = EventKey
					end else
						NonHBList<0, -1> = EventKey
					end
				Next EventKeyIndex
				// Create a new list up to, but not including, the current event.
				For EventKeyIndex = 1 to (vPos - 1)
					NewReactEventList<0, EventKeyIndex> = ReactEventList<0, EventKeyIndex>
				Next EventKeyIndex
			end
			
			If NonHBList NE '' then
				
				LogData    = ''
				LogData<1> = OConv(Datetime(), 'DT2/^H')
				LogData<2> = '3.2'
				LogData<3> = 'Attempting to merge non-hard block consecutive events with matching work orders'
				Schedule_Services('LogActivity', ReactNo, LogData)
				
				// Merge all non-hard block events (merge consecutive events with matching work orders)
				NumNonHBEvents = DCount(NonHBList, @VM)
				// Stop at the second to last event so that we don't overstep the array
				For EventIndex = 1 to (NumNonHBEvents - 1)
					// Look ahead at the next event to see if the work order numbers match. If so, then "merge" the events. 
					// Merging two events
					// - Add scheduled quantities together into first event
					// - Delete second event record
					// - Delete event from NonHBList
					// - Decrement NumNonHBEvents
					// - Update next event pointer
					ThisEventKey      = NonHBList<0, EventIndex>
					ThisEventWONo     = Xlate('SCHED_DET_NG', ThisEventKey, 'WO_NO', 'X')
					ThisEventBlockOut = Xlate('SCHED_DET_NG', ThisEventKey, 'BLOCK_OUT', 'X')
					NextEventKey      = NonHBList<0, EventIndex + 1>
					NextEventWONo     = Xlate('SCHED_DET_NG', NextEventKey, 'WO_NO', 'X')
					NextEventBlockOut = Xlate('SCHED_DET_NG', NextEventKey, 'BLOCK_OUT', 'X')
					// Do not merge blockout events!
					If ( (ThisEventBlockOut NE True$) and (NextEventBlockOut NE True$) and (ThisEventWONo EQ NextEventWONo) ) then
						// Merge the events
						NextEventRec                                  = Database_Services('ReadDataRow', 'SCHED_DET_NG', NextEventKey)
						NextEventWfrQty                               = NextEventRec<SCHED_DET_NG.EVENT_TOTAL_WFRS$>
						NextEventUnprocCass                           = NextEventRec<SCHED_DET_NG.UNPROCESSED_CASS$>
						NextEventProcCass                             = NextEventRec<SCHED_DET_NG.PROCESSED_CASS$>
						NextNextEventKey                              = NonHBList<EventIndex + 2>
						ThisEventRec                                  = Database_Services('ReadDataRow', 'SCHED_DET_NG', ThisEventKey)
						ThisEventWfrQty                               = ThisEventRec<SCHED_DET_NG.EVENT_TOTAL_WFRS$>
						ThisEventUnprocCass                           = ThisEventRec<SCHED_DET_NG.UNPROCESSED_CASS$>
						ThisEventProcCass                             = ThisEventRec<SCHED_DET_NG.PROCESSED_CASS$>
						// Merge wafer quantities and cassette lists
						NewEventWfrQty                                = ThisEventWfrQty + NextEventWfrQty
						ThisEventUnprocCass                           = SRP_Array('Join', ThisEventUnprocCass, NextEventUnprocCass, 'OR', @VM)
						ThisEventProcCass                             = SRP_Array('Join', ThisEventProcCass, NextEventProcCass, 'OR', @VM)
						ThisEventUnprocCass                           = SRP_Array('SortSimpleList', ThisEventUnprocCass, 'AscendingNumbers', @VM)
						ThisEventProcCass                             = SRP_Array('SortSimpleList', ThisEventProcCass, 'AscendingNumbers', @VM)
						
						// Merging split EpiPro events will result in the cassette between the two events
						// being in both the processed and unprocessed cassette lists.
						CutoverCassList = SRP_Array('Join', ThisEventUnprocCass, ThisEventProcCass, 'AND', @VM)
						If CutoverCassList NE '' then
							For each CutoverCassNo in CutoverCassList using @VM
								Locate CutoverCassNo in ThisEventProcCass using @VM setting vPos then
									ThisEventProcCass = Delete(ThisEventProcCass, 0, vPos, 0)
								end
							Next CutoverCassNo
						end
						
						LogData    = ''
						LogData<1> = OConv(Datetime(), 'DT2/^H')
						LogData<2> = '3.3'
						LogData<3> = 'Merging ':ThisEventKey:' with ':ThisEventWfrQty:' wafers and ':NextEventKey:' with ':NextEventWfrQty:' wafers'
						Schedule_Services('LogActivity', ReactNo, LogData)
						
						ThisEventRec<SCHED_DET_NG.EVENT_TOTAL_WFRS$>  = NewEventWfrQty
						ThisEventRec<SCHED_DET_NG.UNPROCESSED_CASS$>  = ThisEventUnprocCass
						ThisEventRec<SCHED_DET_NG.PROCESSED_CASS$>    = ThisEventProcCass
						ThisEventRec<SCHED_DET_NG.EVENT_COMP$>        = False$
						Database_Services('WriteDataRow', 'SCHED_DET_NG', ThisEventKey, ThisEventRec, True$, False$, True$)
						
						LogData    = ''
						LogData<1> = OConv(Datetime(), 'DT2/^H')
						LogData<2> = '3.4'
						LogData<3> = 'Merged events into ':ThisEventKey:' with a new quantity of ':NewEventWfrQty
						Schedule_Services('LogActivity', ReactNo, LogData)                                
						
						Database_Services('DeleteDataRow', 'SCHED_DET_NG', NextEventKey, True$, False$)
						NonHBList                                     = Delete(NonHBList, 0, (EventIndex + 1), 0)
						NumNonHBEvents                               -= 1
					end
					
				Next EventIndex
				
				LogData    = ''
				LogData<1> = OConv(Datetime(), 'DT2/^H')
				LogData<2> = '4'
				LogData<3> = 'Begin rescheduling non-hard block events'
				Schedule_Services('LogActivity', ReactNo, LogData)  
				
				// Reschedule all non-hard block events
				For each EventKey in NonHBList using @VM setting vPos
					
					LogData    = ''
					LogData<1> = OConv(Datetime(), 'DT2/^H')
					LogData<2> = '4.':vPos
					LogData<3> = 'Begin rescheduling event ':EventKey
					Schedule_Services('LogActivity', ReactNo, LogData)                              
					
					EventRec = Schedule_Services('GetScheduleDetail', EventKey)
					BlockOut = EventRec<SCHED_DET_NG.BLOCK_OUT$>
					EpiPro   = (Xlate('REACTOR', ReactNo, 'REACT_TYPE', 'X') EQ 'EPP')
					If BlockOut NE True$ then
						
						LogData    = ''
						LogData<1> = OConv(Datetime(), 'DT2/^H')
						LogData<2> = '4.':vPos:'.1'
						LogData<3> = 'Original event details:'
						Schedule_Services('LogActivity', ReactNo, LogData)
						LogData<3> = 'Event work order: ':EventRec<SCHED_DET_NG.WO_NO$>
						Schedule_Services('LogActivity', ReactNo, LogData)
						Logdata<3> = 'Event start dtm: ':OConv(EventRec<SCHED_DET_NG.START_DTM$>, 'DT2/^H')
						Schedule_Services('LogActivity', ReactNo, LogData)                                
						LogData<3> = 'Event stop dtm: ':OConv(EventRec<SCHED_DET_NG.STOP_DTM$>, 'DT2/^H')
						Schedule_Services('LogActivity', ReactNo, LogData)                                
						LogData<3> = 'Event quantity: ':EventRec<SCHED_DET_NG.EVENT_TOTAL_WFRS$>
						Schedule_Services('LogActivity', ReactNo, LogData)                                
						LogData<3> = 'Event complete: ':OConv(EventRec<SCHED_DET_NG.EVENT_COMP$>, "Btrue,false")
						Schedule_Services('LogActivity', ReactNo, LogData)
						LogData<3> = 'Event unprocessed cassettes: ':EventRec<SCHED_DET_NG.UNPROCESSED_CASS$>
						Schedule_Services('LogActivity', ReactNo, LogData)
						LogData<3> = 'Event processed cassettes: ':EventRec<SCHED_DET_NG.PROCESSED_CASS$>
						Schedule_Services('LogActivity', ReactNo, LogData)   
						
						UnprocessedCass                    = EventRec<SCHED_DET_NG.UNPROCESSED_CASS$>
						ProcessedCass                      = EventRec<SCHED_DET_NG.PROCESSED_CASS$>
						AllCassList                        = SRP_Array('Join', UnprocessedCass, ProcessedCass, 'OR', @VM)
						AllCassList                        = SRP_Array('SortSimpleList', AllCassList, 'AscendingNumbers', @VM)

						// Check each cassette to see if it was voided
						VoidedQty      = 0
						WONo           = EventRec<SCHED_DET_NG.WO_NO$>
						EventWfrs      = EventRec<SCHED_DET_NG.EVENT_TOTAL_WFRS$>
						VoidedCassList = ''
						VoidedWfrQty   = 0
						For each CassNo in AllCassList using @VM
							If Epipro then
								WmInKey = WoNo:'*1*':CassNo
								Voided  = Xlate('WM_IN', WmInKey, 'VOID', 'X')
								CassQty = Xlate('WM_IN', WmInKey, 'WFR_CNT', 'X')
							end else
								WOMatKey = WoNo:'*':CassNo
								Voided   = Xlate('WO_MAT', WOMatKey, 'VOID', 'X')
								CassQty  = Xlate('WO_MAT', WOMatKey, 'WAFER_QTY', 'X')
							end
							If Voided then
								VoidedCassList<0, -1>  = CassNo
								VoidedWfrQty          += CassQty
							end
						Next CassNo
						UnprocessedCass                           = SRP_Array('Join', UnprocessedCass, VoidedCassList, 'NOT', @VM)
						ProcessedCass                             = SRP_Array('Join', ProcessedCass, VoidedCassList, 'NOT', @VM)
						AllCassList                               = SRP_Array('Join', AllCassList, VoidedCassList, 'NOT', @VM)
						EventWfrs                                -= VoidedWfrQty
						EventRec<SCHED_DET_NG.UNPROCESSED_CASS$>  = UnprocessedCass
						EventRec<SCHED_DET_NG.PROCESSED_CASS$>    = ProcessedCass
						EventRec<SCHED_DET_NG.EVENT_TOTAL_WFRS$>  = EventWfrs
						Database_Services('WriteDataRow', 'SCHED_DET_NG', EventKey, EventRec, True$, False$, True$)
						
						// Check each cassette in the event to see if it is now processed or unprocessed (i.e., unload un-signed)
						Schedule_Services('UpdateEventCassLists', EventKey)
						
						EventRec        = Schedule_Services('GetScheduleDetail', EventKey)
						UnprocessedCass = EventRec<SCHED_DET_NG.UNPROCESSED_CASS$>
						ProcessedCass   = EventRec<SCHED_DET_NG.PROCESSED_CASS$>
						AllCassList     = SRP_Array('Join', UnprocessedCass, ProcessedCass, 'OR', @VM)
						AllCassList     = SRP_Array('SortSimpleList', AllCassList, 'AscendingNumbers', @VM)
						EventAdjustment = Schedule_Services('GetEventAdjustment', EventKey)
						EventComp       = (UnprocessedCass EQ '')
						
						If Not(EventRec<SCHED_DET_NG.EVENT_COMP$>) and EventComp then
							LogData    = ''
							LogData<1> = OConv(Datetime(), 'DT2/^H')
							LogData<2> = '4.':vPos:'.1.1'
							LogData<3> = 'Closing event ':EventKey
							Schedule_Services('LogActivity', ReactNo, LogData)
						end
						
						EventRec<SCHED_DET_NG.EVENT_COMP$> = EventComp
						ReactNo                            = EventRec<SCHED_DET_NG.REACT_NO$>
						WOQty                              = Xlate('WO_LOG', WONo, 'QTY', 'X')
						// If work order event is engaged then set start DTM to first cassette DTM in. 
						// If previous event end time overlaps (e.g. soft block finished sooner than scheduled)
						// then truncate it. If work order event is not engaged yet, then set start DTM to previous
						// event's stop DTM.
						RDSNo                              = ''
						DTMIn                              = ''
						
						EventEngaged = False$
						WOMatKeys    = ''
						For each SchedCassNo in AllCassList using @VM setting SchedIndex
							WOMatKeys<0, SchedIndex> = WONo:'*':SchedCassNo
						Next SchedCassNo
						If EpiPro then
							FirstCass = AllCassList<0, 1>
							If FirstCass NE '' then
								WMIKey           = WONo:'*1*':FirstCass
								SchedRDSNos      = Xlate('WM_IN', WMIKey, 'RDS_NO', 'X')
								// Look at the next slot to be scheduled!
								QueryEndDtm      = EventRec<SCHED_DET_NG.STOP_DTM$>
								// Get the number of wafers scheduled up to and including this event.
								// This should give us the correct starting wafer slot.
								ScheduledWfrQty  = Schedule_Services('GetScheduledWfrQty', WONo, QueryEndDtm)
								ScheduledWfrQty -= EventWfrs
								SchedSlot        = Mod(ScheduledWfrQty, 25) + 1
								SchedRDSNos      = Field(SchedRDSNos, @VM, SchedSlot, (25 - SchedSlot))
							end
						end else
							SchedRDSNos  = Xlate('WO_MAT', WOMatKeys, 'RDS_NO', 'X')
						end
						
						SchedDateIns = Xlate('RDS', SchedRDSNos, 'DATE_IN', 'X')
						If SchedDateIns NE '' then
							FirstLoad    = ''
							For each SchedDateIn in SchedDateIns using @VM setting DateInPos
								If SchedDateIn NE '' then
									RDSNo        = SchedRDSNos<0, DateInPos>
									DTMIn = Xlate('RDS', RDSNo, 'DATETIME_IN', 'X')
									EventEngaged = True$
								end
							Until EventEngaged EQ True$
							Next SchedDateIn
						end
						
						If EventEngaged then
							
							LogData       = ''
							LogData<1>    = OConv(Datetime(), 'DT2/^H')
							LogData<2>    = '4.':vPos:'.2'
							LogData<3>    = 'Event ':EventKey:' engaged.'
							Schedule_Services('LogActivity', ReactNo, LogData)
							LogData<3>    = 'Setting start dtm to first RDS load dtm: ':OConv(DTMIn, 'DT2/^H')
							Schedule_Services('LogActivity', ReactNo, LogData)
							// Work order event is engaged so set start DTM to first cassette DTM in.
							EventStartDTM = DTMIn
							// Ensure previous event doesn't overlap.
							CurrNumEvents = DCount(NewReactEventList, @VM)
							If CurrNumEvents GT 0 then
								PrevEventKey  = NewReactEventList<0, CurrNumEvents>
								If PrevEventKey NE '' then
									
									LogData    = ''
									LogData<1> = OConv(Datetime(), 'DT2/^H')
									LogData<2> = '4.':vPos:'.2.1'
									LogData<3> = 'Ensuring previous event ':PrevEventKey:' does not overlap.'
									Schedule_Services('LogActivity', ReactNo, LogData)
									
									PrevEventRec                           = Database_Services('ReadDataRow', 'SCHED_DET_NG', PrevEventKey)
									PrevEventRecOrig                       = PrevEventRec
									PrevEventOrigStartDTM                  = PrevEventRec<SCHED_DET_NG.START_DTM$>
									PrevEventOrigStopDTM                   = PrevEventRec<SCHED_DET_NG.STOP_DTM$>
									PrevEventOrigQty                       = PrevEventRec<SCHED_DET_NG.EVENT_TOTAL_WFRS$>
									PrevEventWONo                          = PrevEventRec<SCHED_DET_NG.WO_NO$>
									PrevEventBlockOut                      = PrevEventRec<SCHED_DET_NG.BLOCK_OUT$>
									PrevEventDuration                      = PrevEventOrigStopDTM - PrevEventOrigStartDTM
									// Ensure previous event is marked as complete
									If PrevEventRec<SCHED_DET_NG.EVENT_COMP$> NE True$ then
										LogData    = ''
										LogData<1> = OConv(Datetime(), 'DT2/^H')
										LogData<2> = '4.':vPos:'.2.1.1'
										LogData<3> = 'Closing previous event ':PrevEventKey:'.'
										Schedule_Services('LogActivity', ReactNo, LogData)                                                
									end
									PrevEventRec<SCHED_DET_NG.EVENT_COMP$> = True$
									If Not(PrevEventBlockOut) then
										// Set previous event's stop datetime to the last processed cassette/run's unload datetime
										PrevEventProcCass     = PrevEventRec<SCHED_DET_NG.PROCESSED_CASS$>
										PrevEventUnprocCass   = PrevEventRec<SCHED_DET_NG.UNPROCESSED_CASS$>
										AllPrevEventSchedCass = SRP_Array('Join', PrevEventProcCass, PrevEventUnprocCass, 'OR', @VM)
										AllPrevEventSchedCass = SRP_Array('SortSimpleList', AllPrevEventSchedCass, 'AscendingNumbers', @VM)
										NumPrevEventSchedCass = DCount(AllPrevEventSchedCass, @VM)
										PrevEventEndCassNo    = AllPrevEventSchedCass<0, NumPrevEventSchedCass>
										
										If EpiPro then
											PrevSchedWfrQty    = Schedule_Services('GetScheduledWfrQty', WONo, PrevEventOrigStopDTM)
											PrevEventEndSlotNo = Mod(PrevSchedWfrQty, 25)
											If PrevEventEndSlotNo EQ 0 then PrevEventEndSlotNo = 25
											WMIKey             = PrevEventWONo:'*1*':PrevEventEndCassNo
											WMIRdsList         = Xlate('WM_IN', WMIKey, 'RDS_NO', 'X')
											RDSNo              = WMIRdsList<0, PrevEventEndSlotNo>
										end else
											WOMatKey = PrevEventWONo:'*':PrevEventEndCassNo
											RDSNo    = Xlate('WO_MAT', WOMatKey, 'RDS_NO', 'X')
										end
										If RDSNo NE '' then
											DtmOut = Xlate('RDS', RDSNo, 'DATETIME_OUT', 'X')
											If ( (DtmOut NE '') and (DtmOut LT PrevEventOrigStopDtm) ) then
												PrevEventStopDTM = DtmOut
												LogData    = ''
												LogData<1>       = OConv(Datetime(), 'DT2/^H')
												LogData<2>       = '4.':vPos:'.2.2'
												LogData<3>       = 'Previous event stop dtm set to last RDS unload dtm: ':OConv(DtmOut, 'DT2/^H')
												Schedule_Services('LogActivity', ReactNo, LogData)                                                           
											end else
												If PrevEventOrigStopDtm LT EventStartDtm then
													PrevEventStopDtm = PrevEventOrigStopDtm
													LogData          = ''
													LogData<1>       = OConv(Datetime(), 'DT2/^H')
													LogData<2>       = '4.':vPos:'.2.2'
													LogData<3>       = 'Previous event stop dtm less than current event start dtm.'
													Schedule_Services('LogActivity', ReactNo, LogData)
													LogData<3>       = 'Preserving previous event stop dtm.'
													Schedule_Services('LogActivity', ReactNo, LogData)
												end else
													PrevEventStopDtm = EventStartDtm
													LogData          = ''
													LogData<1>       = OConv(Datetime(), 'DT2/^H')
													LogData<2>       = '4.':vPos:'.2.2'
													LogData<3>       = 'Previous event stop dtm greater than current event start dtm.'
													Schedule_Services('LogActivity', ReactNo, LogData)
													LogData<3>       = 'Setting previous event stop dtm to current event start dtm.'
													Schedule_Services('LogActivity', ReactNo, LogData)
												end
											end
										end else
											If PrevEventOrigStopDtm LT EventStartDtm then
												PrevEventStopDtm = PrevEventOrigStopDtm
											end else
												PrevEventStopDtm = EventStartDtm
											end
										end
									end else
										
										LogData    = ''
										LogData<1> = OConv(Datetime(), 'DT2/^H')
										LogData<2> = '4.':vPos:'.2.2'
										LogData<3> = 'Previous event is a block event. '
										Schedule_Services('LogActivity', ReactNo, LogData)                                            
										If PrevEventOrigStopDtm GT EventStartDtm then 
											PrevEventStopDtm = EventStartDTM
											LogData<3>       = 'Previous event stop dtm greater than current event start dtm.'
											Schedule_Services('LogActivity', ReactNo, LogData)
											LogData<3>       = 'Setting previous event stop dtm to current event start dtm.'
											Schedule_Services('LogActivity', ReactNo, LogData)
										end else
											PrevEventStopDtm = PrevEventOrigStopDtm
											LogData<3>       = 'Previous event stop dtm less than current event start dtm.'
											Schedule_Services('LogActivity', ReactNo, LogData)
											LogData<3>       = 'Preserving previous event stop dtm.'
											Schedule_Services('LogActivity', ReactNo, LogData)
										end
										Schedule_Services('LogActivity', ReactNo, LogData)        
									end
									PrevEventRec<SCHED_DET_NG.STOP_DTM$> = PrevEventStopDtm
									// Check if events will overlap
									If PrevEventStopDtm GT EventStartDtm then
										LogData       = ''
										LogData<1>    = OConv(Datetime(), 'DT2/^H')
										LogData<2>    = '4.':vPos:'.2.3'
										LogData<3>    = 'Calculated previous event stop dtm greater than current event start dtm.'
										Schedule_Services('LogActivity', ReactNo, LogData)
										LogData<3>    = 'Setting current event start dtm to previous event stop dtm.'
										Schedule_Services('LogActivity', ReactNo, LogData)
										EventStartDtm = PrevEventStopDtm
									end
									
									If PrevEventRec NE PrevEventRecOrig then
										Database_Services('WriteDataRow', 'SCHED_DET_NG', PrevEventKey, PrevEventRec, True$, False$, True$)
									end
								end
							end
							EventStopDTM = CurrDTM + EventAdjustment
						end else
							LogData          = ''
							LogData<1>       = OConv(Datetime(), 'DT2/^H')
							LogData<2>       = '4.':vPos:'.2'
							LogData<3>       = 'Event ':EventKey:' not engaged. '
							Schedule_Services('LogActivity', ReactNo, LogData)
							// Work order event not engaged, so set start DTM to previous event's stop DTM.
							PrevEventKey     = ''
							EventIsHardBlock = False$
							Locate EventKey in OrigReactEventList using @VM setting OrigEventIndex then
							    If OrigEventIndex GT 1 then
							        SearchEventIndex = OrigEventIndex - 1
							        Loop
							            PrevEventKey      = OrigReactEventList<0, SearchEventIndex>
							            EventExists       = RowExists('SCHED_DET_NG', PrevEventKey)
							            If EventExists then EventIsHardBlock = Xlate('SCHED_DET_NG', PrevEventKey, 'HARD_BLOCK', 'X')
							            SearchEventIndex -= 1
							        Until ( ( (EventExists EQ True$) and (EventIsHardBlock NE True$) ) or (SearchEventIndex EQ 1) )
							        Repeat
							    end else
							        PrevEventKey = ''
							    end
							end
							If PrevEventKey NE '' then
								PrevEventRec     = Database_Services('ReadDataRow', 'SCHED_DET_NG', PrevEventKey)
								PrevEventStopDTM = PrevEventRec<SCHED_DET_NG.STOP_DTM$>
								PrevEventComp    = PrevEventRec<SCHED_DET_NG.EVENT_COMP$>
								If ( (PrevEventComp NE True$) and (PrevEventStopDTM GT CurrDTM) ) then
									EventStartDTM = PrevEventStopDTM
									LogData<3>    = 'Previous event not complete.'
									Schedule_Services('LogActivity', ReactNo, LogData)
									LogData<3>    = 'Setting start dtm to to previous event stop dtm: ':OConv(PrevEventStopDTM, 'DT2/^H')
									Schedule_Services('LogActivity', ReactNo, LogData)
								end else
									EventStartDTM = CurrDTM
									LogData<3>    = 'Previous event complete.'
									Schedule_Services('LogActivity', ReactNo, LogData)
									LogData<3>    = 'Setting start dtm to to current dtm: ':OConv(CurrDTM, 'DT2/^H')
									Schedule_Services('LogActivity', ReactNo, LogData)
								end
							end else
								EventStartDTM = CurrDTM
								LogData<3>    = 'No previous event found.'
								Schedule_Services('LogActivity', ReactNo, LogData)
								LogData<3>    = 'Setting start dtm to to current dtm: ':OConv(CurrDTM, 'DT2/^H')
								Schedule_Services('LogActivity', ReactNo, LogData)
							end
							// Set stop DTM to start DTM + event adjustment
							EventStopDTM = EventStartDTM + EventAdjustment
							LogData<3>   = 'New event stop dtm: ':OConv(EventStopDTM, 'DT2/^H')
							Schedule_Services('LogActivity', ReactNo, LogData)
						end
						
						// Check if event is complete (i.e. no more cassettes to process).
						// If event is complete, then set stop DTM to last processed cassette's datetime
						// out. Otherwise, adjust stop DTM to current DTM + event adjustment.
						If EventComp then
							
							LogData    = ''
							LogData<1> = OConv(Datetime(), 'DT2/^H')
							LogData<2> = '4.':vPos:'.3'
							LogData<3> = 'Event ':EventKey:' complete. '
							Schedule_Services('LogActivity', ReactNo, LogData)
							// Unprocessed cassette list must be empty to get here.
							EventRec<SCHED_DET_NG.EVENT_COMP$> = True$
							EventWONo                          = EventRec<SCHED_DET_NG.WO_NO$>
							EventProcCass                      = EventRec<SCHED_DET_NG.PROCESSED_CASS$>
							NumEventProcCass                   = DCount(EventProcCass, @VM)
							EventEndCassNo                     = EventProcCass<0, NumEventProcCass>
							If EpiPro then
								SchedWfrQty    = Schedule_Services('GetScheduledWfrQty', WONo, EventStopDtm)
								EventEndSlotNo = Mod(SchedWfrQty, 25)
								If EventEndSlotNo EQ 0 then EventEndSlotNo = 25
								WMIKey         = EventWONo:'*1*':EventEndCassNo
								WMIRdsList     = Xlate('WM_IN', WMIKey, 'RDS_NO', 'X')
								RDSNo          = WMIRdsList<0, EventEndSlotNo>
							end else
								WOMatKey = EventWONo:'*':EventEndCassNo
								RDSNo    = Xlate('WO_MAT', WOMatKey, 'RDS_NO', 'X')
							end
							If RDSNo NE '' then
								DtmOut = Xlate('RDS', RDSNo, 'DATETIME_OUT', 'X')
								If DtmOut NE '' then
									EventStopDTM = DtmOut
									LogData<3>   = 'Setting event stop dtm to last unloaded RDS ':OConv(DtmOut, 'DT2/^H')
									Schedule_Services('LogActivity', ReactNo, LogData)
								end else
									// Failsafe 
									EventStopDtm = CurrDtm
									LogData<3>   = 'Last RDS does not unloaded.'
									Schedule_Services('LogActivity', ReactNo, LogData)
									LogData<3>   = 'Setting stop dtm to current dtm ':OConv(CurrDtm, 'DT2/^H')
									Schedule_Services('LogActivity', ReactNo, LogData)
								end
							end else
								// Failsafe 
								EventStopDtm = CurrDtm
								LogData<3>   = 'Error! Null last RDS.'
								Schedule_Services('LogActivity', ReactNo, LogData)
								LogData<3>   = 'Setting stop dtm to current dtm ':OConv(CurrDtm, 'DT2/^H')
								Schedule_Services('LogActivity', ReactNo, LogData)
							end                                    
						end
						EventRec<SCHED_DET_NG.START_DTM$> = EventStartDTM
						EventRec<SCHED_DET_NG.STOP_DTM$>  = EventStopDTM
						Database_Services('WriteDataRow', 'SCHED_DET_NG', EventKey, EventRec, True$, False$, True$)
					end else
						// Soft block
						// Adjust start and stop DTMs so that the soft block immediately follows the previous event
						
						LogData    = ''
						LogData<1> = OConv(Datetime(), 'DT2/^H')
						LogData<2> = '4.':vPos:'.1'
						LogData<3> = 'Original event details:'
						Schedule_Services('LogActivity', ReactNo, LogData)                                
						LogData<3> = 'Event work order: ':EventRec<SCHED_DET_NG.WO_NO$>
						Schedule_Services('LogActivity', ReactNo, LogData)                                
						Logdata<3> = 'Event start dtm: ':OConv(EventRec<SCHED_DET_NG.START_DTM$>, 'DT2/^H')
						Schedule_Services('LogActivity', ReactNo, LogData)                                
						LogData<3> = 'Event stop dtm: ':OConv(EventRec<SCHED_DET_NG.STOP_DTM$>, 'DT2/^H')
						Schedule_Services('LogActivity', ReactNo, LogData)                                
						LogData<3> = 'Event quantity: ':EventRec<SCHED_DET_NG.EVENT_TOTAL_WFRS$>
						Schedule_Services('LogActivity', ReactNo, LogData)                                
						LogData<3> = 'Event complete: ':OConv(EventRec<SCHED_DET_NG.EVENT_COMP$>, "Btrue,false")
						Schedule_Services('LogActivity', ReactNo, LogData)
						
						LogData    = ''
						LogData<1> = OConv(Datetime(), 'DT2/^H')
						LogData<2> = '4.':vPos:'.2'
						LogData<3> = 'Adjusting soft block event.'                                
						Schedule_Services('LogActivity', ReactNo, LogData)
						
						CurrNumEvents = DCount(NewReactEventList, @VM)
						PrevEventKey  = NewReactEventList<0, CurrNumEvents>
						EventStartDTM = EventRec<SCHED_DET_NG.START_DTM$>
						EventStopDTM  = EventRec<SCHED_DET_NG.STOP_DTM$>
						EventDuration = EventStopDTM - EventStartDTM
						If PrevEventKey NE '' then
							
							LogData    = ''
							LogData<1> = OConv(Datetime(), 'DT2/^H')
							LogData<2> = '4.':vPos:'.3'
							LogData<3> = 'Previous event found: ':PrevEventKey:' '                                
							Schedule_Services('LogActivity', ReactNo, LogData)                              
							PrevEventRec                      = Database_Services('ReadDataRow', 'SCHED_DET_NG', PrevEventKey)
							PrevEventStopDTM                  = PrevEventRec<SCHED_DET_NG.STOP_DTM$>
							EventStartDTM                     = PrevEventStopDTM
							EventStopDTM                      = EventStartDTM + EventDuration
							EventRec<SCHED_DET_NG.START_DTM$> = EventStartDTM
							EventRec<SCHED_DET_NG.STOP_DTM$>  = EventStopDTM
							Database_Services('WriteDataRow', 'SCHED_DET_NG', EventKey, EventRec, True$, False$, True$)
							
							LogData<3> = 'Setting soft block start dtm to previous event stop dtm.'
							Schedule_Services('LogActivity', ReactNo, LogData)
							LogData<3> = 'Adjusting soft block stop dtm by original event duration.'
							Schedule_Services('LogActivity', ReactNo, LogData)
						end
					end
					// Add event into new reactor event list
					NewReactEventList<0, -1> = EventKey
					
					LogData    = ''
					LogData<1> = OConv(Datetime(), 'DT2/^H')
					LogData<2> = '4.':vPos:'.4'
					LogData<3> = 'Adjusted event details:'
					Schedule_Services('LogActivity', ReactNo, LogData)                            
					LogData<3> = 'Event work order: ':EventRec<SCHED_DET_NG.WO_NO$>
					Schedule_Services('LogActivity', ReactNo, LogData)                            
					Logdata<3> = 'Event start dtm: ':OConv(EventRec<SCHED_DET_NG.START_DTM$>, 'DT2/^H')
					Schedule_Services('LogActivity', ReactNo, LogData)                            
					LogData<3> = 'Event stop dtm: ':OConv(EventRec<SCHED_DET_NG.STOP_DTM$>, 'DT2/^H')
					Schedule_Services('LogActivity', ReactNo, LogData)                            
					LogData<3> = 'Event quantity: ':EventRec<SCHED_DET_NG.EVENT_TOTAL_WFRS$>
					Schedule_Services('LogActivity', ReactNo, LogData)                            
					LogData<3> = 'Event complete: ':OConv(EventRec<SCHED_DET_NG.EVENT_COMP$>, "Btrue,false")
					Schedule_Services('LogActivity', ReactNo, LogData)                               
					
				Next EventKey
			end
			
			If HBList NE '' then
				
				LogData    = ''
				LogData<1> = OConv(Datetime(), 'DT2/^H')
				LogData<2> = '5'
				LogData<3> = 'Hard block event(s) found.'                                
				Schedule_Services('LogActivity', ReactNo, LogData)
				LogData<3> = 'Splitting events and inserting as needed.'
				Schedule_Services('LogActivity', ReactNo, LogData)
				
				// Insert all hard block events.
				//       - Find insert position in list.
				//          - Scan the reactor event list from end to beginning
				//       - If not a block event then 
				//          - Split work order event
				//          - Calculate the number of wafers (round down for whole cassettes) that can be run in
				//            the time window (between event start time and hard block start).
				//          - Calculate event stop time
				//          - Insert hard block into list
				//          - Calculate remaining number of wafers (cassettes)
				//          - Create a new event with remaining number of wafers (start time will be hard block stop time)
				//          - Insert new event after hard block
				//          - Adjust downstream events
				
				For each HBEventKey in HBList using @VM setting vPos
					
					LogData    = ''
					LogData<1> = OConv(Datetime(), 'DT2/^H')
					LogData<2> = '5.':vPos:'.1'
					LogData<3> = 'Rescheduling hard block event ':HBEventKey:'.'                                
					Schedule_Services('LogActivity', ReactNo, LogData)                             
					
					HBInserted      = False$
					HBEventRec      = Schedule_Services('GetScheduleDetail', HBEventKey)
					HBEventStartDTM = HBEventRec<SCHED_DET_NG.START_DTM$>
					HBEventStopDTM  = HBEventRec<SCHED_DET_NG.STOP_DTM$>
					// Find position in event list to insert hard block and determine if we need to split an event
					// (e.g. if there is an event occuring in the hard block time slot).
					
					NumSchedEvents  = DCount(NewReactEventList, @VM)
					If CurrEventKey NE '' then
						Locate CurrEventKey in NewReactEventList using @VM setting StartEventIndex then
							// Traverse forwards
							StepInc = 1
						end else
							// Traverse backwards
							StartEventIndex = NumSchedEvents
							StepInc         = -1
						end
					end else
						// Traverse backwards
						StartEventIndex = NumSchedEvents
						StepInc         = -1
					end
					
					EventIndex = StartEventIndex
					Loop                              
						ThisEventKey = NewReactEventList<0, EventIndex>
						NextEventKey = NewReactEventList<0, EventIndex + StepInc>
						If ThisEventKey NE '' then
							ThisEventRec         = Database_Services('ReadDataRow', 'SCHED_DET_NG', ThisEventKey)
							ThisEventWONo        = ThisEventRec<SCHED_DET_NG.WO_NO$>
							ReactType            = Xlate('WO_LOG', ThisEventWONo, 'REACT_TYPE', 'X')
							ThisEventStartDTM    = ThisEventRec<SCHED_DET_NG.START_DTM$>
							ThisEventStopDTM     = ThisEventRec<SCHED_DET_NG.STOP_DTM$>
							ThisEventTotalWfrQty = ThisEventRec<SCHED_DET_NG.EVENT_TOTAL_WFRS$>
							
							If NextEventKey NE '' then 
							    NextEventStartDtm = Xlate('SCHED_DET_NG', NextEventKey, 'START_DTM', 'X')
							end else
				                NextEventStartDtm = ''
							end
							
							LogData    = ''
							LogData<1> = OConv(Datetime(), 'DT2/^H')
							LogData<2> = '5.':vPos:'.2'
							LogData<3> = 'Checking for insertion point with event ':ThisEventKey:'.'                               
							Schedule_Services('LogActivity', ReactNo, LogData) 
							
							If ( (ThisEventStartDTM LE HBEventStartDTM) and ( (NextEventStartDtm GT HBEventStartDtm) or (NextEventKey EQ '') ) ) then
								
								LogData    = ''
								LogData<1> = OConv(Datetime(), 'DT2/^H')
								LogData<2> = '5.':vPos:'.2.1'
								LogData<3> = 'Found insertion point on event ':ThisEventKey:'.'                               
								Schedule_Services('LogActivity', ReactNo, LogData) 
								
								HBInserted   = True$
								// Found insertion position
								// Need to determine if we need to split
								// Check if event is a block event 
								BlockEvent   = ThisEventRec<SCHED_DET_NG.BLOCK_OUT$>
								TimeConflict = (ThisEventStopDTM GT HBEventStartDTM)
								If ( (BlockEvent NE True$) and (TimeConflict EQ True$) ) then
									
									LogData    = ''
									LogData<1> = OConv(Datetime(), 'DT2/^H')
									LogData<2> = '5.':vPos:'.2.2'
									LogData<3> = 'Confirmed event ':ThisEventKey:' is not a block event and there is time conflict.'                               
									Schedule_Services('LogActivity', ReactNo, LogData)                                             
									
									FirstEventProcCassList   = ''
									FirstEventUnprocCassList = ''
									FirstLoadedRDS           = ''
									LastLoadedRDS            = ''
									LastUnloadedRDS          = ''
									
									Query  = "SELECT RDS WITH WO EQ ":ThisEventWONo:" AND WITH DATETIME_IN GE '"
									Query := OConv(ThisEventStartDTM, 'DT/^S'):"' AND WITH DATETIME_IN LE '"
									Query := OConv(HBEventStartDTM, 'DT/^S'):"' BY DATETIME_IN"
									
									LogData    = ''
									LogData<1> = OConv(Datetime(), 'DT2/^H')
									LogData<2> = '5.':vPos:'.2.3'
									LogData<3> = 'Selecting RDS records to associate with first split event.'                               
									Schedule_Services('LogActivity', ReactNo, LogData)                                                
									
									RList(Query, TARGET_ACTIVELIST$, '', '', '')
									If Not(Get_Status(ErrCode)) then
										RDSKeys = ''
										EOF     = False$
										Loop
											ReadNext RDSKey else EOF = True$
										Until EOF
											RDSKeys<0, -1> = RDSKey
										Repeat
										If RDSKeys NE '' then
											
											LogData    = ''
											LogData<1> = OConv(Datetime(), 'DT2/^H')
											LogData<2> = '5.':vPos:'.2.4'
											LogData<3> = 'RDS keys found. Building unprocessed and processed cassette lists.'                               
											Schedule_Services('LogActivity', ReactNo, LogData)                                                         
											
											For each RDSKey in RDSKeys using @VM
												If FirstLoadedRDS EQ '' then FirstLoadedRDS = RDSKey
												DtmOut = Xlate('RDS', RDSKey, 'DATETIME_OUT', 'X')
												If ReactType EQ 'EPP' then
													WMIKeys = Xlate('RDS', RDSKey, 'WM_IN_KEY', 'X')
													For each WMIKey in WMIKeys using @VM
														CassNo = Field(WMIKey, '*', 3)
														If DtmOut NE '' then
															LastUnloadedRDS = RDSKey
															Locate CassNo in FirstEventProcCassList using @VM setting Dummy else
																FirstEventProcCassList<0, -1> = CassNo
															end
														end else
															LastLoadedRDS = RDSKey
															Locate CassNo in FirstEventUnprocCassList using @VM setting Dummy else
																FirstEventUnprocCassList<0, -1> = CassNo
															end
														end
													Next WMIKey
												end else
													CassNo = Xlate('RDS', RDSKey, 'CASS_NO', 'X')
													If DtmOut NE '' then
														LastUnloadedRDS               = RDSKey
														FirstEventProcCassList<0, -1> = CassNo
													end else
														LastLoadedRDS                   = RDSKey
														FirstEventUnprocCassList<0, -1> = CassNo
													end
												end
											Next RDSKey
											// Cleanup EpiPro cassette lists. 
											FirstEventUnprocCassList = SRP_Array('Join', FirstEventUnprocCassList, FirstEventProcCassList, 'NOT', @VM)
										end else
											LogData    = ''
											LogData<1> = OConv(Datetime(), 'DT2/^H')
											LogData<2> = '5.':vPos:'.2.4'
											LogData<3> = 'No RDS keys found.'                               
											Schedule_Services('LogActivity', ReactNo, LogData)                                                     
										end
									end
									
									FirstEventProcCassCount   = DCount(FirstEventProcCassList, @VM)
									FirstEventUnprocCassCount = DCount(FirstEventUnprocCassList, @VM)
									FirstEventRemCassCount    = 0
									If HBEventStartDTM GT CurrDtm then
										
										LogData    = ''
										LogData<1> = OConv(Datetime(), 'DT2/^H')
										LogData<2> = '5.':vPos:'.2.4.1'
										LogData<3> = 'Hard block event not engaged yet.'                              
										Schedule_Services('LogActivity', ReactNo, LogData)
										LogData<3> = 'Estimating the number of cassettes that can be run before the hard block.'
										Schedule_Services('LogActivity', ReactNo, LogData)
										
										TimeWindow                 = HBEventStartDTM - CurrDTM
										// Calculate number of whole cassettes we can run in this window
										FirstEventRemWfrEstimate   = Schedule_Services('GetWaferEstimate', ThisEventWONo, TimeWindow)
										FirstEventRemCassCount    += SRP_Math('FLOOR', (FirstEventRemWfrEstimate / 25) )
										FirstEventUnprocCassCount += FirstEventRemCassCount
									end
									
									// If it is estimated that additional cassettes can run before the hard block event,
									// then we'll work with cassette quantities of 25. If it is estimated that no addtional 
									// whole cassettes can run before the hard block, then determine the exact event wafer
									// quantity to ensure the event begins and ends on an RDS and not on a whole cassette.
									
									If FirstEventRemCassCount GT 0 then
										
										LogData    = ''
										LogData<1> = OConv(Datetime(), 'DT2/^H')
										LogData<2> = '5.':vPos:'.2.5'
										LogData<3> = 'At least one full cassette can run, so estimating stop dtm of event.'                               
										Schedule_Services('LogActivity', ReactNo, LogData)                                                 
										
										FirstEventUnprocWfrQty = FirstEventUnprocCassCount * 25
										// Determine stop DTM of first split event
										EventRuntime           = Schedule_Services('GetEventDuration', ThisEventWONo, FirstEventUnprocWfrQty)
										ThisEventStopDTM       = CurrDTM + EventRuntime
										If ThisEventStopDTM GT HBEventStartDtm then
											ThisEventStopDtm = HBEventStartDtm
										end
										// Set new wafer qty for first split
										FirstEventProcWfrQty   = FirstEventProcCassCount * 25
										FirstEventWfrQty       = FirstEventUnprocWfrQty + FirstEventProcWfrQty
										FirstEventCassCount    = FirstEventWfrQty / 25
										
										LogData    = ''
										LogData<1> = OConv(Datetime(), 'DT2/^H')
										LogData<2> = '5.':vPos:'.2.5.1'
										LogData<3> = 'First split event wafer quantity ':FirstEventWfrQty                               
										Schedule_Services('LogActivity', ReactNo, LogData)                                                 
										
										// Determine unprocessed cass list
										If FirstEventProcCassCount GT 0 then
											LastProcCassNo = FirstEventProcCassList<0, FirstEventProcCassCount>
										end else
											LastProcCassNo = 0
										end
										FirstEventUnprocCassList = ''
										For CassIndex = 1 to FirstEventUnprocCassCount
											FirstEventUnprocCassList<0, -1> = (LastProcCassNo + CassIndex)
										Next CassIndex
										
									end else
										
										LogData    = ''
										LogData<1> = OConv(Datetime(), 'DT2/^H')
										LogData<2> = '5.':vPos:'.2.5'
										LogData<3> = 'It is estimated that no additional cassettes can be processed in the time left before the hard block event.'                          
										Schedule_Services('LogActivity', ReactNo, LogData)                                                 
										
										// It is estimated that no additional cassettes can be processed in the time left before the hard block event.
										If FirstEventUnprocCassList NE '' then
											ThisEventStopDTM = HBEventStartDTM
											If ReactType EQ 'EPP' then
												// Find slot event starts on
												FirstLoadedWMIs       = Xlate('RDS', FirstLoadedRDS, 'WM_IN_KEY', 'X')
												FirstLoadedWMI        = FirstLoadedWMIs[1, 'F':@VM]
												FirstLoadedWMIRdsList = Xlate('WM_IN', FirstLoadedWMI, 'RDS_NO', 'X')
												Done                  = False$
												For SlotIndex = 1 to 25 Step 1
													If FirstLoadedWMIRdsList<0, SlotIndex> EQ FirstLoadedRDS then
														StartSlot = SlotIndex
														Done      = True$
													end
												Until Done
												Next StartSlot
												// Find slot event stops on
												LastLoadedWMIs        = Xlate('RDS', LastLoadedRDS, 'WM_IN_KEY', 'X')
												LastLoadedWMI         = LastLoadedWMIs[-1, 'B':@VM]
												LastLoadedWMIRdsList  = Xlate('WM_IN', LastLoadedWMI, 'RDS_NO', 'X')
												Done                  = False$
												For SlotIndex = 25 to 1 Step -1
													If LastLoadedWMIRdsList<0, SlotIndex> EQ LastLoadedRDS then
														StopSlot = SlotIndex
														Done     = True$
													end
												Until Done
												Next SlotIndex
												FirstEventWfrQty = ( (FirstEventProcCassCount - 1) * 25 ) + (25 - StartSlot + 1) + ( (FirstEventUnprocCassCount - 1) * 25 ) + StopSlot
											end else
												FirstEventWfrQty = ( FirstEventProcCassCount * 25 ) + ( FirstEventUnprocCassCount * 25)
											end
											
											LogData    = ''
											LogData<1> = OConv(Datetime(), 'DT2/^H')
											LogData<2> = '5.':vPos:'.2.6'
											LogData<3> = 'First split event has unprocessed cassettes left.'                        
											Schedule_Services('LogActivity', ReactNo, LogData)                                                         
											
										end else
											// Get the last RDS that ran and use that as the end of this event.
											LastUnloadDtm    = Xlate('RDS', LastUnloadedRDS, 'DATETIME_OUT', 'X')
											If LastUnloadDtm LT HBEventStartDtm then
												ThisEventStopDtm = LastUnloadDtm
											end else
												ThisEventStopDTM = HBEventStartDTM
											end
											If ReactType EQ 'EPP' then
												// Find slot event starts on
												FirstLoadedWMIs       = Xlate('RDS', FirstLoadedRDS, 'WM_IN_KEY', 'X')
												FirstLoadedWMI        = FirstLoadedWMIs[1, 'F':@VM]
												FirstLoadedWMIRdsList = Xlate('WM_IN', FirstLoadedWMI, 'RDS_NO', 'X')
												Done                  = False$
												For SlotIndex = 1 to 25 Step 1
													If FirstLoadedWMIRdsList<0, SlotIndex> EQ FirstLoadedRDS then
														StartSlot = SlotIndex
														Done      = True$
													end
												Until Done
												Next StartSlot
												
												// Find slot event stops on
												LastUnloadedWMIs       = Xlate('RDS', LastUnloadedRDS, 'WM_IN_KEY', 'X')
												LastUnloadedWMI        = LastUnloadedWMIs[-1, 'B':@VM]
												LastUnloadedWMIRdsList = Xlate('WM_IN', LastUnloadedWMI, 'RDS_NO', 'X')
												Done                 = False$
												For SlotIndex = 25 to 1 Step -1
													If LastUnloadedWMIRdsList<0, SlotIndex> EQ LastUnloadedRDS then
														StopSlot = SlotIndex
														Done     = True$
													end
												Until Done
												Next SlotIndex
												FirstEventWfrQty = ( (FirstEventProcCassCount - 2) * 25 ) + (25 - StartSlot + 1) + StopSlot
											end else
												FirstEventWfrQty = ( FirstEventProcCassCount * 25 )
											end
											ThisEventRec<SCHED_DET_NG.EVENT_COMP$> = True$
											
											LogData    = ''
											LogData<1> = OConv(Datetime(), 'DT2/^H')
											LogData<2> = '5.':vPos:'.2.6'
											LogData<3> = 'First split event no unprocessed cassettes left.'
											Schedule_Services('LogActivity', ReactNo, LogData)
											LogData<3> = 'Marking event complete.'
											Schedule_Services('LogActivity', ReactNo, LogData)
											
										end
									end
									
									// Update first split event
									If FirstEventWfrQty GT 100 then
										ThisEventRec<SCHED_DET_NG.PROCESSED_CASS$>   = FirstEventProcCassList
										ThisEventRec<SCHED_DET_NG.UNPROCESSED_CASS$> = FirstEventUnprocCassList
										ThisEventRec<SCHED_DET_NG.STOP_DTM$>         = ThisEventStopDTM
										ThisEventRec<SCHED_DET_NG.EVENT_TOTAL_WFRS$> = FirstEventWfrQty
										Database_Services('WriteDataRow', 'SCHED_DET_NG', ThisEventKey, ThisEventRec, True$, False$, True$)
										
										LogData    = ''
										LogData<1> = OConv(Datetime(), 'DT2/^H')
										LogData<2> = '5.':vPos:'.2.7'
										LogData<3> = 'First split event updated. Wafer quantity: ':FirstEventWfrQty:'. Stop dtm ':ThisEventStopDtm
										Schedule_Services('LogActivity', ReactNo, LogData)                                                       
										
									end else
										// Event is too small to schedule. Move the wafers to the second split event.
										FirstEventWfrQty   = 0
										Database_Services('DeleteDataRow', 'SCHED_DET_NG', ThisEventKey, True$, False$)
										NewReactEventList  = Delete(NewReactEventList, 0, EventIndex, 0)
										EventIndex        -= 1
										
										LogData    = ''
										LogData<1> = OConv(Datetime(), 'DT2/^H')
										LogData<2> = '5.':vPos:'.2.7'
										LogData<3> = 'First split event wafer quantity too small.'
										Schedule_Services('LogActivity', ReactNo, LogData)
										LogData<3> = 'Deleting event and moving all wafers to second split event.'
										Schedule_Services('LogActivity', ReactNo, LogData)
										
									end
									
									// Insert hard block into reactor event list after first split
									HBEventIndex                                 = EventIndex + 1
									NewReactEventList                            = Insert(NewReactEventList, 0, HBEventIndex, 0, HBEventKey)
									
									// Calculate second split event quantity                           
									SecondEventWfrQty = ThisEventTotalWfrQty - FirstEventWfrQty
									
									LogData    = ''
									LogData<1> = OConv(Datetime(), 'DT2/^H')
									LogData<2> = '5.':vPos:'.2.8'
									LogData<3> = 'Determining if second split event is necessary.'
									Schedule_Services('LogActivity', ReactNo, LogData)
									LogData<3> = 'Second split event wafer quantity ':SecondEventWfrQty:'.'
									Schedule_Services('LogActivity', ReactNo, LogData)
									
									If SecondEventWfrQty GT 0 then
										
										SecondEventUnprocCassList = ''
										SecondEventProcCassList   = '' 
										
										// Look at the next slot to be scheduled!
										// Get the number of wafers scheduled up to this event.
										// This should give us the correct starting wafer slot.
										PrevSchedWfrQty    = Schedule_Services('GetScheduledWfrQty', ThisEventWONo, HBEventStopDTM)
										StartCassNo        = SRP_Math('CEILING', ( (PrevSchedWfrQty + 1) / 25))
										If StartCassNo EQ 0 then StartCassNo = 1
										SecondEventNumCass = SRP_Math('CEILING', (SecondEventWfrQty / 25))
										FirstRDS           = ''
										
										If ReactType EQ 'EPP' then
											// Example: Event starts on WM_IN cassette 6 (i.e. 172125*1*6), slot 13.
											StartSlotNo = Mod(PrevSchedWfrQty, 25) + 1
											StopCassNo  = StartCassNo + SecondEventNumCass - 1
											StopSlotNo  = Mod((PrevSchedWfrQty + SecondEventWfrQty), 25)
											If StopSlotNo EQ 0 then StopSlotNo = 25
											
											For CassIndex = 1 to SecondEventNumCass
												CassNo           = (StartCassNo + CassIndex - 1)
												WMIKey           = ThisEventWONo:'*1*':CassNo
												WMIRdsKeys       = Xlate('WM_IN', WMIKey, 'RDS_NO', 'X')
												EventCassRDSKeys = ''
												Begin Case
													Case CassNo EQ StartCassNo
														StartIndex = StartSlotNo
														StopIndex  = 25
													Case CassNo EQ StopCassNo
														StartIndex = 1
														StopIndex  = StopSlotNo
													Case ( (CassNo EQ StartCassNo) and (CassNo EQ StopCassNo) )
														StartIndex = StartSlotNo
														StopIndex  = StopSlotNo
													Case Otherwise$
														StartIndex = 1
														StopIndex  = 25
												End Case
												For SlotIndex = StartIndex to StopIndex
													If FirstRDS EQ '' then FirstRDS = WMIRdsKeys<0, SlotIndex>
													EventCassRDSKeys<0, -1> = WMIRdsKeys<0, SlotIndex>
												Next SlotIndex
												EventCassRDSKeys = SRP_Array('Clean', EventCassRDSKeys, 'TrimAndMakeUnique', @VM)
												// Now we have a list of RDS keys that only pertain to this event.
												If EventCassRDSKeys NE '' then
													// Are all of the RDS runs unloaded?
													NumUnloadedRDS = 0
													UnloadDtms     = Xlate('RDS', EventCassRDSKeys, 'DATETIME_OUT', 'X')
													If UnloadDtms NE '' then
														For each UnloadDtm in UnloadDtms using @VM
															If UnloadDtm NE '' then NumUnloadedRds += 1
														Next UnloadDtm
													end
													NumRDS        = DCount(EventCassRDSKeys, @VM)
													CassProcessed = (NumRds EQ NumUnloadedRds)
												end else
													CassProcessed = False$
												end
												If CassProcessed then
													SecondEventProcCassList<0, -1> = CassNo
												end else
													SecondEventUnprocCassList<0, -1> = CassNo
												end
											Next CassIndex
											SecondEventProcCassList = SRP_Array('Join', SecondEventProcCassList, SecondEventUnprocCassList, 'NOT', @VM)
											
										end else
											// Example: Event starts on WO_MAT cassette 7. We don't need to schedule down to the
											//          wafer levels with non-epipro work order events.
											For CassIndex = 1 to SecondEventNumCass
												CassNo        = (StartCassNo + CassIndex - 1)
												WOMatKey      = ThisEventWONo:'*':CassNo
												RDSNo         = Xlate('WO_MAT', WOMatKey, 'RDS_NO', 'X')
												If FirstRDS EQ '' then FirstRDS = RdsNo
												UnloadDtm     = Xlate('RDS', RDSNo, 'DATETIME_OUT', 'X')
												CassProcessed = (UnloadDtm NE '')
												If CassProcessed then
													SecondEventProcCassList<0, -1> = CassNo
												end else
													SecondEventUnprocCassList<0, -1> = CassNo
												end
											Next CassIndex
										end
										
										SecondEventStartDtm = HBEventStopDtm
										
										If FirstRDS NE '' then
											FirstLoadDtm = Xlate('RDS', FirstRDS, 'DATETIME_IN', 'X')
											If FirstLoadDtm GT HBEventStopDTM then
												SecondEventStartDtm = FirstLoadDtm
											end
										end
										
										SecondEventKey                                 = RTI_CreateGUID()
										SecondEventRec                                 = ''
										NewEventDuration                               = Schedule_Services('GetEventDuration', ThisEventWONo, SecondEventWfrQty)
										SecondEventRec<SCHED_DET_NG.REACT_NO$>         = ThisEventRec<SCHED_DET_NG.REACT_NO$>
										SecondEventRec<SCHED_DET_NG.WO_NO$>            = ThisEventRec<SCHED_DET_NG.WO_NO$>
										SecondEventRec<SCHED_DET_NG.START_DTM$>        = SecondEventStartDTM
										SecondEventRec<SCHED_DET_NG.STOP_DTM$>         = SecondEventStartDTM + NewEventDuration
										SecondEventRec<SCHED_DET_NG.DESC$>             = ThisEventRec<SCHED_DET_NG.DESC$>
										SecondEventRec<SCHED_DET_NG.EVENT_TOTAL_WFRS$> = SecondEventWfrQty
										SecondEventRec<SCHED_DET_NG.PROCESSED_CASS$>   = SecondEventProcCassList
										SecondEventRec<SCHED_DET_NG.UNPROCESSED_CASS$> = SecondEventUnprocCassList
										Database_Services('WriteDataRow', 'SCHED_DET_NG', SecondEventKey, SecondEventRec)
										// Add second split event to event list
										NewEventIndex                                  = EventIndex + 2
										NewReactEventList                              = Insert(NewReactEventList, 0, NewEventIndex, 0, SecondEventKey)
										
										LogData    = ''
										LogData<1> = OConv(Datetime(), 'DT2/^H')
										LogData<2> = '5.':vPos:'.2.9'
										LogData<3> = 'Second split event details:'
										Schedule_Services('LogActivity', ReactNo, LogData)                                                
										LogData<3> = 'Event work order: ':SecondEventRec<SCHED_DET_NG.WO_NO$>
										Schedule_Services('LogActivity', ReactNo, LogData)                                                
										Logdata<3> = 'Event start dtm: ':OConv(SecondEventRec<SCHED_DET_NG.START_DTM$>, 'DT2/^H')
										Schedule_Services('LogActivity', ReactNo, LogData)                                                
										LogData<3> = 'Event stop dtm: ':OConv(SecondEventRec<SCHED_DET_NG.STOP_DTM$>, 'DT2/^H')
										Schedule_Services('LogActivity', ReactNo, LogData)                                                
										LogData<3> = 'Event quantity: ':SecondEventRec<SCHED_DET_NG.EVENT_TOTAL_WFRS$>
										Schedule_Services('LogActivity', ReactNo, LogData)                                                
										LogData<3> = 'Event complete: ':OConv(SecondEventRec<SCHED_DET_NG.EVENT_COMP$>, "Btrue,false")
										Schedule_Services('LogActivity', ReactNo, LogData)    
										
									end
									
									// Adjust downstream events
									LogData    = ''
									LogData<1> = OConv(Datetime(), 'DT2/^H')
									LogData<2> = '6.':vPos
									LogData<3> = 'Begin adjusting downstream events.'
									Schedule_Services('LogActivity', ReactNo, LogData)   
									
									NumSchedEvents                          = DCount(NewReactEventList, @VM)
									For AdjustmentIndex = (EventIndex + 3) to NumSchedEvents
										AdjustEventKey                          = NewReactEventList<0, AdjustmentIndex>
										AdjustEventRec                          = Database_Services('ReadDataRow', 'SCHED_DET_NG', AdjustEventKey)
										AdjustEventWONo                         = AdjustEventRec<SCHED_DET_NG.WO_NO$>
										AdjustEventWfrQty                       = AdjustEventRec<SCHED_DET_NG.EVENT_TOTAL_WFRS$>
										PrevEventKey                            = NewReactEventList<0, AdjustmentIndex - 1>
										PrevEventStopDTM                        = Xlate('SCHED_DET_NG', PrevEventKey, 'STOP_DTM', 'X')
										// Set new start and stop DTMs
										AdjustEventStartDTM                     = PrevEventStopDTM
										AdjustEventDuration                     = Schedule_Services('GetEventDuration', AdjustEventWONo, AdjustEventWfrQty)
										AdjustEventStopDTM                      = AdjustEventStartDTM + AdjustEventDuration
										AdjustEventRec<SCHED_DET_NG.START_DTM$> = AdjustEventStartDTM
										AdjustEventRec<SCHED_DET_NG.STOP_DTM$>  = AdjustEventStopDTM
										Database_Services('WriteDataRow', 'SCHED_DET_NG', AdjustEventKey, AdjustEventRec, True$, False$, True$)
										
										LogData<1> = OConv(Datetime(), 'DT2/^H')
										LogData<2> = '6.':vPos:'.':AdjustmentIndex
										LogData<3> = 'Event ':AdjustEventKey:' adjusted.'
										Schedule_Services('LogActivity', ReactNo, LogData)
										LogData<3> = 'New start dtm ':OConv(AdjustEventStartDtm, 'DT2/^H'):'.'
										Schedule_Services('LogActivity', ReactNo, LogData)
										LogData<3> = 'New stop dtm ':OConv(AdjustEventStopDtm, 'DT2/^H'):'.'
										Schedule_Services('LogActivity', ReactNo, LogData)
										
									Next AdjustmentIndex
								end else
									// No time conflict, so just insert hard block event
									HBEventIndex                       = EventIndex + 1
									NewReactEventList                  = Insert(NewReactEventList, 0, HBEventIndex, 0, HBEventKey)
									
									LogData    = ''
									LogData<1> = OConv(Datetime(), 'DT2/^H')
									LogData<2> = '5.':vPos:'.2.2'
									LogData<3> = 'No time conflict found between event ':ThisEventKey:' and ':HBEventKey:'.'                     
									Schedule_Services('LogActivity', ReactNo, LogData)
									LogData<3> = 'Inserting hard block without splitting.'
									Schedule_Services('LogActivity', ReactNo, LogData)
									
								end
							end
						end
						EventIndex += StepInc
					Until ( HBInserted or (EventIndex LT 0) or (EventIndex GT NumSchedEvents) )
					Repeat
				Next HBEventKey
			end else
				LogData    = ''
				LogData<1> = OConv(Datetime(), 'DT2/^H')
				LogData<2> = '5'
				LogData<3> = 'No hard block events to reschedule.'                              
				Schedule_Services('LogActivity', ReactNo, LogData) 
			end
		end
		
		LogData    = ''
		LogData<1> = OConv(Datetime(), 'DT2/^H')
		LogData<2> = '6'
		LogData<3> = 'Begin cleaning out "zombie events".'                     
		Schedule_Services('LogActivity', ReactNo, LogData)                 
		
		NumEvents = DCount(NewReactEventList, @VM)
		For EventIndex = 1 to NumEvents
			EventID       = NewReactEventList<0, EventIndex>
			EventRec      = Database_Services('ReadDataRow', 'SCHED_DET_NG', EventID)
			EventWONo     = EventRec<SCHED_DET_NG.WO_NO$> 
			EventBlockOut = EventRec<SCHED_DET_NG.BLOCK_OUT$>
			If ( (EventWONo EQ '') and (EventBlockOut NE True$) ) then
				// This is a zombie, so delete it (there's a bug up above somewhere creating or not
				// deleting events as necessary.
				NewReactEventList = Delete(NewReactEventList, 0, EventIndex, 0)
				Database_Services('DeleteDataRow', 'SCHED_DET_NG', EventID, True$, False$)
				
				LogData    = ''
				LogData<1> = OConv(Datetime(), 'DT2/^H')
				LogData<2> = '6.':EventIndex
				LogData<3> = 'Zombie event ':EventID:' found and deleted.'           
				Schedule_Services('LogActivity', ReactNo, LogData)                             
				
			end
		Next EventIndex
		ReactNo = OrigReactNo
		// Update reactor event list
		If ( (NewReactEventList NE '') and (NewReactEventList NE OrigReactEventList) ) then
			ReactRec                        = Database_Services('ReadDataRow', 'REACTOR', ReactNo)
			// Clean out duplicate event keys (there's a bug up above somewhere creating them)
			NewReactEventList               = SRP_Array('Clean', NewReactEventList, 'TrimAndMakeUnique', @VM)
			ReactRec<REACTOR_SCHED_EVENTS$> = NewReactEventList
			Database_Services('WriteDataRow', 'REACTOR', ReactNo, ReactRec, True$, False$, True$)
			
			LogData    = ''
			LogData<1> = OConv(Datetime(), 'DT2/^H')
			LogData<2> = '7'
			LogData<3> = 'Writing new event list for reactor ':ReactNo:'.'     
			Schedule_Services('LogActivity', ReactNo, LogData)
			
		end
		Schedule_Services('SendRefreshMessage')
		
		Unlock hSysLists, ServiceKeyID:'*':ReactNo else Null
	end
	
end service


//----------------------------------------------------------------------------------------------------------------------
// AddSchedEvent
//
// Saves the event to the database.
//----------------------------------------------------------------------------------------------------------------------
Service AddSchedEvent(ReactorNo, WorkOrderNo, StartDTM, StopDTM, Description, WaferQty)

	// Create a unique key ID for the new event
	ErrorMsg                                    = ''
	NewEventKeyID                               = RTI_CreateGUID()
	NewEventRec                                 = ''
	NewEventRec<SCHED_DET_NG.REACT_NO$>         = ReactorNo
	NewEventRec<SCHED_DET_NG.WO_NO$>            = WorkOrderNo
	NewEventRec<SCHED_DET_NG.START_DTM$>        = StartDTM
	NewEventRec<SCHED_DET_NG.DESC$>             = Description
	NewEventRec<SCHED_DET_NG.EVENT_TOTAL_WFRS$> = WaferQty
	EpiPro                                      = (Xlate('WO_LOG', WorkOrderNo, 'REACT_TYPE', 'X') EQ 'EPP')
	CurrDTM                                     = Datetime()
	EventProcCassList                           = ''
	EventUnprocCassList                         = ''
	LastLoadedRDS                               = ''
	LastUnloadedRDS                             = ''
	// Determine start cassette and slot number 
	PrevSchedQty                                = Schedule_Services('GetScheduledWfrQty', WorkOrderNo)
	VoidedQty                                   = Work_Order_Services('GetVoidedWaferCount', WorkOrderNo)
	StartCass     = SRP_Math('CEILING', ( (PrevSchedQty + VoidedQty + 1) / 25))
	If StartCass EQ 0 then StartCass = 1	
	StartSlot     = Mod(PrevSchedQty, 25) + 1
	TotalSchedQty = PrevSchedQty + WaferQty + VoidedQty
	StopCass      = SRP_Math('CEILING', ( TotalSchedQty / 25 ) )
	StopSlot      = Mod(TotalSchedQty, 25)
	If StopSlot EQ 0 then StopSlot = 25
	// Get list of RDS keys associated with this event if they exist
	RDSKeys       = ''
	If EpiPro then 
		For CassNo = StartCass to StopCass
			WMIKey = WorkOrderNo:'*1*':CassNo
			If RowExists('WM_IN', WMIKey) then 
				WMIRec = Database_Services('ReadDataRow', 'WM_IN', WMIKey)
			end else
				// Material not received yet
				WMIRec = ''
			end
			If Error_Services('NoError') then
				Voided = WMIRec<WM_IN_VOID$>
				If Not(Voided) then 
					WMIRdsList = WMIRec<WM_IN_RDS_NO$>
					If WMIRdsList NE '' then
						Begin Case
							Case ( (CassNo EQ StartCass) and (CassNo EQ StopCass) )
								StartIndex = StartSlot
								StopIndex  = StopSlot
							Case CassNo EQ StartCass
								StartIndex = StartSlot
								StopIndex  = 25
							Case CassNo EQ StopCass
								StartIndex = 1
								StopIndex  = StopSlot
							Case Otherwise$
								StartIndex = 1
								StopIndex  = 25 
						End Case
						For SlotIndex = StartIndex to StopIndex
							RDSKeys<0, -1> = WMIRdsList<0, SlotIndex>
						Next SlotIndex
					end else
						// No RDS keys exist for this cassette yet, so it's unprocessed
						EventUnprocCassList<0, -1> = CassNo
					end
				end				
			end else
				ErrorMsg = Error_Services('GetMessage')
			end
		Until (ErrorMsg NE '')
		Next CassNo
		
		If (ErrorMsg EQ '') then 
			If RDSKeys NE '' then
				RDSKeys = SRP_Array('Clean', RDSKeys, 'TrimAndMakeUnique', @VM)
				For each RDSKey in RDSKeys using @VM setting vPos
					RDSRec = Database_Services('ReadDataRow', 'RDS', RDSKey)
					If Error_Services('NoError') then
						DateOut = RDSRec<RDS_DATE_OUT$>
						TimeOut = RDSRec<RDS_TIME_OUT$>
						TimeOut = TimeOut/86400
						TimeOut = SRP_Math('ROUND', TimeOut, 5)
						TimeOut = TimeOut[2, 6]
						DtmOut  = DateOut:TimeOut
					end else
						ErrorMsg = Error_Services('GetMessage')
					end
					If (ErrorMsg EQ '' ) then 
						WMIKeys = Xlate('RDS', RDSKey, 'WM_IN_KEY', 'X')
						For each WMIKey in WMIKeys using @VM
							CassNo = Field(WMIKey, '*', 3)
							If DtmOut NE '' then
								LastUnloadedRDS = RDSKey
								Locate CassNo in EventProcCassList using @VM setting Dummy else
									EventProcCassList<0, -1> = CassNo
								end
							end else
								Locate CassNo in EventUnprocCassList using @VM setting Dummy else
									EventUnprocCassList<0, -1> = CassNo
								end
							end
						Next WMIKey
					end
				Until (ErrorMsg NE '')
				Next RDSKey
			end
			
			EventUnprocCassList = SRP_Array('SortSimpleList', EventUnprocCassList, 'AscendingNumbers', @VM)
			// This line ensures EpiPro cassettes are not recorded as both processed and unprocessed.
			EventProcCassList   = SRP_Array('Join', EventProcCassList, EventUnprocCassList, 'NOT', @VM)
		end
		
	end else
		
		For CassNo = StartCass to StopCass
			DtmOut   = ''
			WOMatKey = WorkOrderNo:'*':CassNo
			If RowExists('WO_MAT', WOMatKey) then 
				WOMatRec = Database_Services('ReadDataRow', 'WO_MAT', WOMatKey)
			end else
				// Material not received yet
				WOMatRec = ''
			end
			If Error_Services('NoError') then
				Voided = WOMatRec<WO_MAT_VOID$>
				If Not(Voided) then 
					RDSKey = WOMatRec<WO_MAT_RDS_NO$>
					If (RDSKey NE '') then 
						RDSRec = Database_Services('ReadDataRow', 'RDS', RDSKey)
						If Error_Services('NoError') then
							DateOut = RDSRec<RDS_DATE_OUT$>
							TimeOut = RDSRec<RDS_TIME_OUT$>
							TimeOut = TimeOut/86400
							TimeOut = SRP_Math('ROUND', TimeOut, 5)
							TimeOut = TimeOut[2, 6]
							DtmOut  = DateOut:TimeOut
						end else
							ErrorMsg = Error_Services('GetMessage')
						end
					end
				end
			end else
				ErrorMsg = Error_Services('GetMessage')
			end
			
			If (ErrorMsg EQ '') then 
				If (DtmOut NE '') then
					LastUnloadedRDS = RDSKey
					Locate CassNo in EventProcCassList using @VM setting Dummy else
						EventProcCassList<0, -1> = CassNo
					end
				end else
					Locate CassNo in EventUnprocCassList using @VM setting Dummy else
						EventUnprocCassList<0, -1> = CassNo
					end
				end
			end
		Until (ErrorMsg NE '')
		Next CassNo
	end
	
	If (ErrorMsg EQ '') then 
	
		EventComp = (EventUnprocCassList EQ '')
		If EventComp then StopDtm = Xlate('RDS', LastUnloadedRDS, 'DATETIME_OUT', 'X')
		
		// Update event
		NewEventRec<SCHED_DET_NG.EVENT_COMP$>       = EventComp
		NewEventRec<SCHED_DET_NG.PROCESSED_CASS$>   = EventProcCassList
		NewEventRec<SCHED_DET_NG.UNPROCESSED_CASS$> = EventUnprocCassList
		NewEventRec<SCHED_DET_NG.STOP_DTM$>         = StopDtm
		Database_Services('WriteDataRow', 'SCHED_DET_NG', NewEventKeyID, NewEventRec)               
		
		If Error_Services('NoError') then
			// Determine where to insert the event in the REACTOR record linked list  
			// Add the event to the REACTOR record event list
			ReactRec         = Database_Services('ReadDataRow', 'REACTOR', ReactorNo)
			ReactSchedEvents = ReactRec<REACTOR_SCHED_EVENTS$>
			CurrNumEvents    = DCount(ReactSchedEvents, @VM)  
			// Add our new event key ID to the reactor event list
			NewNumEvents     = CurrNumEvents + 1
			InsertPos = ''
			For EventIndex = 1 to CurrNumEvents Step 1
				ThisEventKey = ReactSchedEvents<0, EventIndex>
				ThisStartDTM = Xlate('SCHED_DET_NG', ThisEventKey, 'START_DTM', 'X')
				IF ThisStartDTM GT StartDTM then
					// We found our location
					InsertPos = EventIndex
				end
			Until InsertPos NE ''
			Next EventIndex
			If InsertPos EQ '' then InsertPos = NewNumEvents
			ReactSchedEvents                = Insert(ReactSchedEvents, 0, InsertPos, 0, NewEventKeyID)
			ReactRec<REACTOR_SCHED_EVENTS$> = ReactSchedEvents
			Database_Services('WriteDataRow', 'REACTOR', ReactorNo, ReactRec)
			
			// Check if there is an overlap with the previous event. If so, then set previous event's stop DTM to this event's
			// start DTM.
			If NewNumEvents GT 1 then
				PrevEventKey = ReactSchedEvents<0, (NewNumEvents - 1)>
				If PrevEventKey NE '' then
					PrevEventRec     = Database_Services('ReadDataRow', 'SCHED_DET_NG', PrevEventKey)
					PrevEventStopDTM = PrevEventRec<SCHED_DET_NG.STOP_DTM$>
					If PrevEventStopDTM GT StartDTM then
						PrevEventStopDTM = StartDTM
						PrevEventRec<SCHED_DET_NG.STOP_DTM$> = PrevEventStopDTM
						Database_Services('WriteDataRow', 'SCHED_DET_NG', PrevEventKey, PrevEventRec, True$, False$, True$)
					end
				end
			end
			// Record schedule change in SCHED_HIST table
			Schedule_Services('AddSchedHist', NewEventKeyID, ReactorNo, 'ADD', @User4)
			Response = NewEventKeyID
		end
	end
	
	If ErrorMsg NE '' then Error_Services('Add', ErrorMsg)
	
end service


Service AddSchedHist(SchedDetNGKey, ReactorNo, Action=ACTIONS, User)
	
	Error = ''
	If ( (SchedDetNGKey NE '') and (ReactorNo NE '') and (Action NE '') and (User NE '') ) then
		SchedHistRec                               = ''
		SchedHistRec<SCHED_HIST.SCHED_DET_NG_KEY$> = SchedDetNGKey
		SchedHistRec<SCHED_HIST.REACTOR$>          = ReactorNo
		SchedHistRec<SCHED_HIST.ACTION$>           = Action
		SchedHistRec<SCHED_HIST.USER$>             = User
		SchedHistKey                               = NextKey('SCHED_HIST')
		If SchedHistKey NE '' then
			Database_Services('WriteDataRow', 'SCHED_HIST', SchedHistKey, SchedHistRec)
			If Error_Services('HasError') then Error = Error_Services('GetMessage')
		end else
			Error = 'Error in ':Service:' service. Error retrieving new SCHED_HIST key.'
		end
	end else
		Error = 'Error in ':Service:' service. Null parameter passed in.'
	end
	If Error NE '' then Error_Services('Add', Error)
	
end service


//----------------------------------------------------------------------------------------------------------------------
// ModifySchedEvent
//
// Modifies an event's details.
//----------------------------------------------------------------------------------------------------------------------
Service ModifySchedEvent(SchedDetNGKey, ReactorNo, WorkOrderNo, StartDTM, StopDTM, Description, WaferQty, AdjustNextEvent=BOOLEAN)

	Response = False$
	If SchedDetNGKey NE '' then
		EventRec = Database_Services('ReadDataRow', 'SCHED_DET_NG', SchedDetNGKey)
		If Error_Services('NoError') then
			If AdjustNextEvent EQ '' then AdjustNextEvent = True$
			CurrDTM = Datetime()
			If ReactorNo    NE '' then EventRec<SCHED_DET_NG.REACT_NO$>  = ReactorNo
			If WorkOrderNo  NE '' then EventRec<SCHED_DET_NG.WO_NO$>     = WorkOrderNo
			If StartDTM     NE '' then EventRec<SCHED_DET_NG.START_DTM$> = StartDTM
			If StopDTM      NE '' then EventRec<SCHED_DET_NG.STOP_DTM$>  = StopDTM
			If Description  NE '' then EventRec<SCHED_DET_NG.DESC$>      = Description
			OrigWfrQty = EventRec<SCHED_DET_NG.EVENT_TOTAL_WFRS$>
			// If the user is just changing the description the wafer quantity may not be passed in.
			If WaferQty EQ '' then WaferQty = OrigWfrQty
			If OrigWfrQty NE WaferQty then
				
				ReactorNo           = EventRec<SCHED_DET_NG.REACT_NO$>
				WorkOrderNo         = EventRec<SCHED_DET_NG.WO_NO$>
				StartDTM            = EventRec<SCHED_DET_NG.START_DTM$>
				StopDTM             = EventRec<SCHED_DET_NG.STOP_DTM$>
				EpiPro              = ( Xlate('REACTOR', ReactorNo, 'REACT_TYPE', 'X') EQ 'EPP' )
				EventProcCassList   = ''
				EventUnprocCassList = ''
				LastLoadedRDS       = ''
				LastUnloadedRDS     = ''
				
				// Determine start cassette and slot number 
				PrevSchedQty  = Schedule_Services('GetScheduledWfrQty', WorkOrderNo, StartDtm)
				StartCass     = SRP_Math('CEILING', ( (PrevSchedQty + 1) / 25))
				If StartCass EQ 0 then StartCass = 1
				StartSlot     = Mod(PrevSchedQty, 25) + 1
				TotalSchedQty = PrevSchedQty + WaferQty
				StopCass      = SRP_Math('CEILING', ( TotalSchedQty / 25 ) )
				StopSlot      = Mod(TotalSchedQty, 25)
				If StopSlot EQ 0 then StopSlot = 25
				// Get list of RDS keys associated with this event if they exist
				RDSKeys       = ''
				
				If EpiPro then 
					For CassNo = StartCass to StopCass
						WMIKey             = WorkOrderNo:'*1*':CassNo
						WMIRdsList         = Xlate('WM_IN', WMIKey, 'RDS_NO', 'X')
						If WMIRdsList NE '' then
							Begin Case
								Case ( (CassNo EQ StartCass) and (CassNo EQ StopCass) )
									StartIndex = StartSlot
									StopIndex  = StopSlot
								Case CassNo EQ StartCass
									StartIndex = StartSlot
									StopIndex  = 25
								Case CassNo EQ StopCass
									StartIndex = 1
									StopIndex  = StopSlot
								Case Otherwise$
									StartIndex = 1
									StopIndex  = 25 
							End Case
							For SlotIndex = StartIndex to StopIndex
								RDSKeys<0, -1> = WMIRdsList<0, SlotIndex>
							Next SlotIndex
						end else
							// No RDS keys exist for this cassette yet, so it's unprocessed
							EventUnprocCassList<0, -1> = CassNo
						end
					Next CassNo
					
					If RDSKeys NE '' then
						RDSKeys = SRP_Array('Clean', RDSKeys, 'TrimAndMakeUnique', @VM)
						For each RDSKey in RDSKeys using @VM setting vPos
							DtmOut  = Xlate('RDS', RDSKey, 'DATETIME_OUT', 'X')
							WMIKeys = Xlate('RDS', RDSKey, 'WM_IN_KEY', 'X')
							For each WMIKey in WMIKeys using @VM
								CassNo = Field(WMIKey, '*', 3)
								If DtmOut NE '' then
									LastUnloadedRDS = RDSKey
									Locate CassNo in EventProcCassList using @VM setting Dummy else
										EventProcCassList<0, -1> = CassNo
									end
								end else
									Locate CassNo in EventUnprocCassList using @VM setting Dummy else
										EventUnprocCassList<0, -1> = CassNo
									end
								end
							Next WMIKey                            
						Next RDSKey
					end
					
					EventUnprocCassList = SRP_Array('SortSimpleList', EventUnprocCassList, 'AscendingNumbers', @VM)
					// This line ensures EpiPro cassettes are not recorded as both processed and unprocessed.
					EventProcCassList   = SRP_Array('Join', EventProcCassList, EventUnprocCassList, 'NOT', @VM)
					
				end else
					
					For CassNo = StartCass to StopCass
						WOMatKey           = WorkOrderNo:'*':CassNo
						RDSKey             = Xlate('WO_MAT', WOMatKey, 'RDS_NO', 'X')
						DtmOut             = Xlate('RDS', RDSKey, 'DATETIME_OUT', 'X')
						If DtmOut NE '' then
							LastUnloadedRDS = RDSKey
							Locate CassNo in EventProcCassList using @VM setting Dummy else
								EventProcCassList<0, -1> = CassNo
							end
						end else
							Locate CassNo in EventUnprocCassList using @VM setting Dummy else
								EventUnprocCassList<0, -1> = CassNo
							end
						end
					Next CassNo
				end
				
				EventComp = (EventUnprocCassList EQ '')
				If EventComp then StopDtm = Xlate('RDS', LastUnloadedRDS, 'DATETIME_OUT', 'X')
				
				// Update event
				EventRec<SCHED_DET_NG.EVENT_COMP$>       = EventComp
				EventRec<SCHED_DET_NG.PROCESSED_CASS$>   = EventProcCassList
				EventRec<SCHED_DET_NG.UNPROCESSED_CASS$> = EventUnprocCassList
				EventRec<SCHED_DET_NG.STOP_DTM$>         = StopDtm
				EventRec<SCHED_DET_NG.EVENT_TOTAL_WFRS$> = WaferQty
				Database_Services('WriteDataRow', 'SCHED_DET_NG', SchedDetNGKey, EventRec, True$, False$, True$)               
				
				If Error_Services('NoError') then
					Schedule_Services('AddSchedHist', SchedDetNGKey, ReactorNo, 'MODIFY', @User4)
					Response = True$
				end
				
				If AdjustNextEvent then
					// Adjust downstream events if they share the same work order. If the above event increased its
					// wafer quantity then the next event will be reduced by the delta and vice versa.
					ModifyWfrQty   = WaferQty - OrigWfrQty
					Query          = 'SELECT SCHED_DET_NG WITH WO_NO EQ ':WorkOrderNo:' AND WITH START_DTM GT '
					Query         := Quote(OConv(StartDtm, 'DT/^3H')):' BY START_DTM'                  
					GoSub ClearCursors
					Set_Status(0)
					RList(Query, TARGET_ACTIVELIST$, '', '', '')
					errCode        = ''
					Done           = False$
					DownStreamKey  = ''
					If Get_Status(errCode) then
						ErrorMsg = 'Error retrieving event list from the ':Service:' module. Error code':errCode
						Error_Services('Add', ErrorMsg)       
					end else
						EOF = False$
						ReadNext DownStreamKey else EOF = True$
						If ( Not(EOF) and (DownStreamKey NE SchedDetNGKey) ) then
							DownStreamRec     = Database_Services('ReadDataRow', 'SCHED_DET_NG', DownStreamKey)
							OrigDownStreamQty = DownStreamRec<SCHED_DET_NG.EVENT_TOTAL_WFRS$>
							NewDownStreamQty  = OrigDownStreamQty - ModifyWfrQty
							Schedule_Services('ModifySchedEvent', DownStreamKey, '', '', '', '', '', NewDownStreamQty, False$)
						end
						GoSub ClearCursors
					end
				end
			end else
				// User is likely just adding a comment
				Database_Services('WriteDataRow', 'SCHED_DET_NG', SchedDetNGKey, EventRec, True$, False$, True$)               
				If Error_Services('NoError') then
					Schedule_Services('AddSchedHist', SchedDetNGKey, ReactorNo, 'MODIFY', @User4)
					Response = True$
				end
			end
		end
	end
	
end service


Service GetBlockEvents(ReactorNo, StartDTM, StopDTM, BlockGroup=BLOCK_GROUP)
	
	ScheduleDetailKeys = ''
	
	If Not(Num(StartDTM)) then StartDTM = Iconv(StartDTM, 'DT')
	If Not(Num(StopDTM)) then StopDTM = Iconv(StopDTM, 'DT')
	
	GoSub ClearCursors
	Set_Status(0)
	Query     = ''
	If ReactorNo NE '' then
		Query = 'SELECT SCHED_DET_NG WITH REACT_NO EQ ':ReactorNo
		RList(Query, TARGET_ACTIVELIST$, '', '', '')
	end
	Begin Case
		Case ((StartDTM NE '') and (StopDTM NE ''))
			Query  = 'SELECT SCHED_DET_NG WITH START_DTM BETWEEN ':Quote(OConv(StartDTM, 'DT/^3H')):' AND ':Quote(OConv(StopDTM, 'DT/^3H')):' OR '
			Query := 'WITH STOP_DTM BETWEEN ':Quote(OConv(StartDTM, 'DT/^3H')):' AND ':Quote(OConv(StopDTM, 'DT/^3H'))
			RList(Query, TARGET_ACTIVELIST$, '', '', '')
		Case ((StartDTM NE '') and (StopDTM EQ ''))
			Query = 'SELECT SCHED_DET_NG WITH START_DTM GE ':Quote(OConv(StartDTM, 'DT/^3H'))
			RList(Query, TARGET_ACTIVELIST$, '', '', '')
		Case ((StartDTM EQ '') and (StopDTM NE ''))
			Query = 'SELECT SCHED_DET_NG WITH STOP_DTM LE ':Quote(OConv(StopDTM, 'DT/^3H'))
			RList(Query, TARGET_ACTIVELIST$, '', '', '')
		Case ((StartDTM EQ '') and (StopDTM EQ ''))
			Null
	End Case
	Begin Case
		Case BlockGroup _EQC 'SOFT_BLOCK'
			Query = 'SELECT SCHED_DET_NG WITH SOFT_BLOCK EQ ':True$
			RList(Query, TARGET_ACTIVELIST$, '', '', '')
		Case BlockGroup _EQC 'HARD_BLOCK'
			Query = 'SELECT SCHED_DET_NG WITH HARD_BLOCK EQ ':True$
			RList(Query, TARGET_ACTIVELIST$, '', '', '')
		Case Otherwise$
			// Get all block events
			Query = 'SELECT SCHED_DET_NG WITH BLOCK_OUT EQ ':True$
			RList(Query, TARGET_ACTIVELIST$, '', '', '')
	End Case
	errCode            = ''
	If Get_Status(errCode) then
		ErrorMsg = 'Error retrieving event list from the ':Service:' module. Error code':errCode
		Error_Services('Add', ErrorMsg)       
	end else
		EOF = False$
		Loop
			ReadNext SchedDetNGKey Else EOF = True$
		Until EOF EQ True$
			ScheduleDetailKeys<-1> = SchedDetNGKey
		Repeat
	end
	GoSub ClearCursors
	
	Response    = ScheduleDetailKeys
	
end service


Service AddBlockOutEvent(ReactorNo, StartDTM, StopDTM, BlockType, Description, Options)
	
	// Create a unique key ID for the new event
	NewEventKeyID                             = RTI_CreateGUID()
	NewEventRec                               = ''
	NewEventRec<SCHED_DET_NG.REACT_NO$>       = ReactorNo
	NewEventRec<SCHED_DET_NG.START_DTM$>      = StartDTM
	NewEventRec<SCHED_DET_NG.STOP_DTM$>       = StopDTM
	NewEventRec<SCHED_DET_NG.DESC$>           = Description
	NewEventRec<SCHED_DET_NG.BLOCK_OUT$>      = True$
	NewEventRec<SCHED_DET_NG.BLOCK_OUT_TYPE$> = BlockType
	
	// Determine where to insert the event in the REACTOR record linked list  
	// Add the event to the REACTOR record linked list
	ReactRec         = Database_Services('ReadDataRow', 'REACTOR', ReactorNo)
	ReactSchedEvents = ReactRec<REACTOR_SCHED_EVENTS$>
	CurrNumEvents    = DCount(ReactSchedEvents, @VM)  
	ReactRec         = Database_Services('ReadDataRow', 'REACTOR', ReactorNo)
	ReactSchedEvents = ReactRec<REACTOR_SCHED_EVENTS$>
	CurrNumEvents    = DCount(ReactSchedEvents, @VM)  
	// Add our new event key ID to the reactor event list
	NewNumEvents     = CurrNumEvents + 1
	InsertPos        = ''
	For EventIndex = 1 to CurrNumEvents Step 1
		ThisEventKey = ReactSchedEvents<0, EventIndex>
		ThisStartDTM = Xlate('SCHED_DET_NG', ThisEventKey, 'START_DTM', 'X')
		IF ThisStartDTM GT StartDTM then
			// We found our location
			InsertPos = EventIndex
		end
	Until InsertPos NE ''
	Next EventIndex
	If InsertPos EQ '' then InsertPos = NewNumEvents
	ReactSchedEvents = Insert(ReactSchedEvents, 0, InsertPos, 0, NewEventKeyID)
	ReactRec<REACTOR_SCHED_EVENTS$> = ReactSchedEvents
	Database_Services('WriteDataRow', 'REACTOR', ReactorNo, ReactRec)
	Database_Services('WriteDataRow', 'SCHED_DET_NG', NewEventKeyID, NewEventRec)
	
	// Record schedule change in SCHED_HIST table
	Schedule_Services('AddSchedHist', NewEventKeyID, ReactorNo, 'ADD', @User4)
	
	Response = NewEventKeyID
	
end service


//----------------------------------------------------------------------------------------------------------------------
// GetScheduleDetail
//
// Returns the SCHED_DET_NG row for the indicated Key ID.
//----------------------------------------------------------------------------------------------------------------------
Service GetScheduleDetail(ScheduleKeyID)
	
	ScheduleDetail = ''
	TableName      = 'SCHED_DET_NG'
	
	If ScheduleKeyID NE '' then
		ScheduleDetail  = Database_Services('ReadDataRow', Tablename, ScheduleKeyID)
	end else
		Error_Services('Add', 'ScheduleKeyID argument was missing from the ' : Service : ' service.')
	end
	
	Response    = ScheduleDetail
	
end service


//----------------------------------------------------------------------------------------------------------------------
// SetScheduleDetail
//
// Creates or updates the SCHED_DET_NG row for the indicated Key ID.
//----------------------------------------------------------------------------------------------------------------------
Service SetScheduleDetail(ScheduleKeyID, ScheduleDetail)
	
	If (ScheduleKeyID NE '') AND (ScheduleDetail NE '') then
		TableName = 'SCHED_DET_NG'
		Database_Services('WriteDataRow', Tablename, ScheduleKeyID, ScheduleDetail, True$)
	end else
		Error_Services('Add', 'ScheduleKeyID or ScheduleDetail argument was missing from the ' : Service : ' service.') 
	end
	
end service


//----------------------------------------------------------------------------------------------------------------------
// DeleteScheduleDetail
//
// Deletes the SCHED_DET_NG row for the indicated Key IDs.
//----------------------------------------------------------------------------------------------------------------------
Service DeleteScheduleDetail(ScheduleKeyIDs)
	
	If ScheduleKeyIDs NE '' then
		TableName = 'SCHED_DET_NG'
		For Each ScheduleKeyID in ScheduleKeyIDs using @FM
			ReactNo     = Xlate(TableName, ScheduleKeyID, 'REACT_NO', 'X')
			ReactRec    = Database_Services('ReadDataRow', 'REACTOR', ReactNo)
			ReactEvents = ReactRec<REACTOR_SCHED_EVENTS$>
			Locate ScheduleKeyID in ReactEvents using @VM setting vPos then
				ReactEvents                     = Delete(ReactEvents, 0, vPos, 0)
				ReactRec<REACTOR_SCHED_EVENTS$> = ReactEvents
				Database_Services('WriteDataRow', 'REACTOR', ReactNo, ReactRec, True$, False$, True$)
			end
			Database_Services('DeleteDataRow', Tablename, ScheduleKeyID, True$)
		Next ScheduleKeyID
	end else
		Error_Services('Add', 'ScheduleKeyID argument was missing from the ' : Service : ' service.')
	end
	
end service


//----------------------------------------------------------------------------------------------------------------------
// GetAdjustedDays
//
// Returns the calculated adjusted number of days needed to process the remaining wafers for the indicated work order.
// Note: this can return a negative value if there are more scheduled events needed to complete the remaining wafers.
//----------------------------------------------------------------------------------------------------------------------
Service GetAdjustedDays(WorkOrderNo, ReactorNo, EventWafers)
	
	DaysNeeded = ''
	If WorkOrderNo NE '' then
		WorkOrderDetail     = Work_Order_Services('GetWorkOrderLogDetail', WorkOrderNo)
		If Error_Services('NoError') then
			If EventWafers EQ '' then
				// Get estimate for all remaining wafers in the work order.
				EventWafers     = Work_Order_Services('GetRemainingWafers', WorkOrderNo)
			end
			EpiPartNo           = WorkOrderDetail<WO_LOG_EPI_PART_NO$>
			TotalWafers         = WorkOrderDetail<WO_LOG_WO_QTY$>
			WOReactorType       = WorkOrderDetail<WO_LOG_REACT_TYPE$>
			WOClosedFlag        = WorkOrderDetail<WO_LOG_CLOSE_DATE$> NE ''
			Begin Case
				Case WOReactorType _eqc 'ASM+'  ; WOReactorType = 'ASM+'
				Case WOReactorType _eqc 'EPP'   ; WOReactorType = 'EpiPro'
				Case WOReactorType _eqc 'EPS'   ; WOReactorType = 'ASM'
				Case WOReactorType _eqc 'HTR'   ; WOReactorType = 'HTR'
				Case WOReactorType _eqc 'GAN'   ; WOReactorType = 'GaN'
				Case WOReactorType _eqc ''	    ; WOReactorType = '***'
			End Case
			WafersPerDay = Epi_Part_Services('GetAdjustedWafersPerDayScheduler', EpiPartNo, WOReactorType)
			
			If ( (WafersPerDay NE 0) and (WafersPerDay NE '') ) then
				DaysNeeded = (EventWafers / WafersPerDay)
			end
		end else
			GoSub LogError
		end
	end else
		Error_Services('Add', 'WorkOrderNo argument was missing from the ' : Service : ' service.')
		GoSub LogError
	end
	Response = DaysNeeded
	
end service


Service GetEventAdjustment(SchedDetKey)
	
	DaysNeeded = ''
	
	If SchedDetKey NE '' then
		SchedDetRec = Schedule_Services('GetScheduleDetail', SchedDetKey)
		BlockOut    = SchedDetRec<SCHED_DET_NG.BLOCK_OUT$>
		If BlockOut EQ True$ then
			// Simply return the original duration of the event (i.e. there is no adjustment)
			EventStartDTM = SchedDetRec<SCHED_DET_NG.START_DTM$>
			EventStopDTM  = SchedDetRec<SCHED_DET_NG.STOP_DTM$>
			DaysNeeded    = EventStopDTM - EventStartDTM
		end else
			// Calculate new duration necessary to complete event.
			WorkOrderNo     = SchedDetRec<SCHED_DET_NG.WO_NO$>
			WorkOrderDetail = Work_Order_Services('GetWorkOrderLogDetail', WorkOrderNo)
			If Error_Services('NoError') then
				CurrDTM            = Datetime()
				EventRemWfrs       = Schedule_Services('GetEventRemainingWfrs', SchedDetKey)
				If Error_Services('NoError') then
					EpiPartNo           = WorkOrderDetail<WO_LOG_EPI_PART_NO$>
					TotalWafers         = WorkOrderDetail<WO_LOG_WO_QTY$>
					WOReactorType       = WorkOrderDetail<WO_LOG_REACT_TYPE$>
					WOClosedFlag        = WorkOrderDetail<WO_LOG_CLOSE_DATE$> NE ''
					Begin Case
						Case WOReactorType _eqc 'ASM+'  ; WOReactorType = 'ASM+'
						Case WOReactorType _eqc 'EPP'   ; WOReactorType = 'EpiPro'
						Case WOReactorType _eqc 'EPS'   ; WOReactorType = 'ASM'
						Case WOReactorType _eqc 'HTR'   ; WOReactorType = 'HTR'
						Case WOReactorType _eqc 'GAN'   ; WOReactorType = 'GaN'
						Case WOReactorType _eqc ''	    ; WOReactorType = '***'
					End Case
					WafersPerDay = Epi_Part_Services('GetAdjustedWafersPerDayScheduler', EpiPartNo, WOReactorType)
					If Error_Services('NoError') then
						DaysNeeded = (EventRemWfrs / WafersPerDay)
					end else
						GoSub LogError
					end
				end else
					GoSub LogError
				end
			end else
				GoSub LogError
			end
		end
	end else
		Error_Services('Add', 'SchedDetKey argument was missing from the ' : Service : ' service.')
		GoSub LogError
	end
	Response = DaysNeeded
	
end service


Service GetWafersPerDayAdjusted(WONo)
	
	If WONo NE '' then
		WorkOrderDetail = Work_Order_Services('GetWorkOrderLogDetail', WONo)
		If Error_Services('NoError') then
			If Error_Services('NoError') then
				EpiPartNo           = WorkOrderDetail<WO_LOG_EPI_PART_NO$>
				WOReactorType       = WorkOrderDetail<WO_LOG_REACT_TYPE$>
				Begin Case
					Case WOReactorType _eqc 'ASM+'  ; WOReactorType = 'ASM+'
					Case WOReactorType _eqc 'EPP'   ; WOReactorType = 'EpiPro'
					Case WOReactorType _eqc 'EPS'   ; WOReactorType = 'ASM'
					Case WOReactorType _eqc 'HTR'   ; WOReactorType = 'HTR'
					Case WOReactorType _eqc 'GAN'   ; WOReactorType = 'GaN'
					Case WOReactorType _eqc ''	    ; WOReactorType = '***'
				End Case
				WafersPerDay = Epi_Part_Services('GetAdjustedWafersPerDayScheduler', EpiPartNo, WOReactorType)
				If Error_Services('NoError') then
					Response = WafersPerDay
				end else
					GoSub LogError
				end
			end else
				GoSub LogError
			end
		end else
			GoSub LogError
		end
	end else
		Error_Services('Add', 'WONo argument was missing from the ' : Service : ' service.')
		GoSub LogError
	end
	
end service


Service GetWaferEstimate(WONo, TimeWindow)
	
	If ( (WONo NE '') and (TimeWindow NE '') ) then
		WafersPerDayAdjusted = Schedule_Services('GetWafersPerDayAdjusted', WONo)
		WaferEstimate        = WafersPerDayAdjusted * TimeWindow
		Response             = WaferEstimate
	end
	
end service


Service GetEventDuration(WONo, WaferQty)
	
	If ( (WONo NE '') and (WaferQty NE '') ) then
		WafersPerDayAdjusted = Schedule_Services('GetWafersPerDayAdjusted', WONo)
		If ( (WafersPerDayAdjusted NE '') and (WafersPerDayAdjusted GT 0) ) then
			TimeEstimate         = WaferQty / WafersPerDayAdjusted
			Response             = TimeEstimate
		end else
			WorkOrderDetail = Work_Order_Services('GetWorkOrderLogDetail', WONo)
			If WorkOrderDetail NE '' then
				EpiPartNo = WorkOrderDetail<WO_LOG_EPI_PART_NO$>
				ReactType = WorkOrderDetail<WO_LOG_REACT_TYPE$>
				ErrorMsg  = 'Error in ':Service:' service. Wafers per day value not set for EpiPart ':EpiPartNo:' for reactor type 'ReactType:'.'
			end else
				ErrorMsg  = 'Error in ':Service:' service. Wafers per day value not set for work order ':WONo:'.'
			end
		end
	end
	
end service


//----------------------------------------------------------------------------------------------------------------------
// AutoScheduler
//
// Analyzes all work orders events for the current date for all reactors. Determines the number of days required for
// each work order to complete the remaining number of wafers. Each work order is adjusted automatically based on the
// adjuted number of days calculated.
//----------------------------------------------------------------------------------------------------------------------
Service AutoScheduler()
	
	HaveLock = Database_Services('GetKeyIDLock', 'APP_INFO', 'AUTO_SCHEDULER_REACTOR')
	If HaveLock then
		ReactNos = Reactor_Services('GetReactorNumbers')
		If Error_Services('NoError') then
			LastReactNo = ''
			If RowExists('APP_INFO', 'AUTO_SCHEDULER_REACTOR') then 
				LastReactNo = Database_Services('ReadDataRow', 'APP_INFO', 'AUTO_SCHEDULER_REACTOR')
			end
			If LastReactNo NE '' then 
				Locate LastReactNo in ReactNos using @FM setting fPos then
					fPos += 1
				end
			end else
				fPos = 1
			end
			NextReactNo = ReactNos<fPos>
			// WriteDataRow service will release the lock. 
			Database_Services('WriteDataRow', 'APP_INFO', 'AUTO_SCHEDULER_REACTOR', NextReactNo, True$, False$, False$)
			Schedule_Services('AdjustScheduleEvents', NextReactNo)
		end
	end
	
end service


//----------------------------------------------------------------------------------------------------------------------
// LogActivity
//
// Logs the current activity indicated in the LogData argument. This should be reactor based.
//----------------------------------------------------------------------------------------------------------------------
Service LogActivity(ReactorNo, LogData, EmailNotification=BOOLEAN)
	
	If ReactorNo EQ '' then ReactorNo = 0
	LogPath     = Environment_Services('GetApplicationRootPath') : '\LogFiles\Scheduler\' : ReactorNo
	LogFileName = Date[7, 4] : '-' : Date[1, 2] : '-' : Date[4, 2] : ' Scheduler Log.csv'
	objLog      = Logging_Services('NewLog', LogPath, LogFilename, CRLF$, ' ', Headers, ColumnWidths, False$, False$)
	If EmailNotification then
		Recipients   = 'DANIEL_ST'
		SentFrom     = @USER4
		Subject      = 'Scheduler Notification'
		Message      = LogData
		Swap @FM with ' ' in Message
		AttachWindow = ''
		AttachKey    = ''
		SendToGroup  = ''
		Parms        = Recipients:@RM:SentFrom:@RM:Subject:@RM:Message:@RM:AttachWindow:@RM:AttachKey:@RM:SendToGroup
		obj_Notes('Create',Parms)
	end
	Logging_Services('AppendLog', objLog, LogData, @RM, @FM, False$)
	
end service


Service GetScheduledWfrQty(WorkOrderNo, EndDtm)
	
	ScheduledQty = ''
	TableName    = "SCHED_DET_NG"
	Open "DICT.":TableName To hTableDict then
		ScheduledQty = 0
		Column       = "WO_NO"
		QueryParam   = WorkOrderNo
		Query        = Column:@VM:QueryParam:@FM
		If EndDtm NE '' then
			If Not(Num(EndDtm)) then EndDtm = IConv(EndDtm, 'DT')
			Query := "STOP_DTM":@VM:'<=':EndDtm:@FM
		end
		Keylist      = ""
		Option       = "S"
		Flag         = ""
		Btree.Extract(Query, TableName, hTableDict, Keylist, Option, Flag)
		If Flag EQ 0 then
			For each KeyID in Keylist using @VM
				ScheduledQty += Xlate('SCHED_DET_NG', KeyID, 'EVENT_TOTAL_WFRS', 'X')        
			Next KeyID
		end
	end
	Response = ScheduledQty
	
end service


Service GetScheduledEvents(WorkOrderNo)
	
	Keylist      = ""
	TableName    = "SCHED_DET_NG"
	Open "DICT.":TableName To hTableDict then
		ScheduledQty = 0
		Column       = "WO_NO"
		QueryParam   = WorkOrderNo
		Query        = Column:@VM:QueryParam:@FM
		Option       = "S"
		Flag         = ""
		Btree.Extract(Query, TableName, hTableDict, Keylist, Option, Flag)
		If Flag EQ 0 then
			Error_Services('Add', 'Error in ':Service:' service. Error calling Btree.Extract.')
		end
	end
	Response = KeyList
	
end service


Service GetScheduledCassettes(WorkOrderNo)
	
	ScheduledCassettes = ''
	TableName          = "SCHED_DET_NG"
	Open "DICT.":TableName To hTableDict then
		ScheduledQty = 0
		Column       = "WO_NO"
		QueryParam   = WorkOrderNo
		Query        = Column:@VM:QueryParam:@FM
		Keylist      = ""
		Option       = "S"
		Flag         = ""
		Btree.Extract(Query, TableName, hTableDict, Keylist, Option, Flag)
		If Flag EQ 0 then
			For each KeyID in Keylist using @VM
				UnprocessedCass = Xlate('SCHED_DET_NG', KeyID, 'UNPROCESSED_CASS', 'X')
				ProcessedCass   = Xlate('SCHED_DET_NG', KeyID, 'PROCESSED_CASS', 'X')
				ScheduledCassettes = SRP_Array('Join', ScheduledCassettes, UnprocessedCass, 'OR', @VM)
				ScheduledCassettes = SRP_Array('Join', ScheduledCassettes, ProcessedCass, 'OR', @VM)
			Next KeyID
		end
	end
	ScheduledCassettes = SRP_Array('SortSimpleList', ScheduledCassettes, 'AscendingNumbers', @VM)
	Response           = ScheduledCassettes
	
end service


Service GetUnscheduledWfrQty(WorkOrderNo)
	
	If WorkOrderNo NE '' then
		ScheduledWfrQty   = Schedule_Services('GetScheduledWfrQty', WorkOrderNo)
		WorkOrderWfrQty   = Xlate('WO_LOG', WorkOrderNo, 'QTY', 'X')
		UnscheduledWfrQty = WorkOrderWfrQty - ScheduledWfrQty
		Response          = UnscheduledWfrQty
	end
	
end service


//----------------------------------------------------------------------------------------------------------------------
// GetAvailableWorkOrders
//
// Returns an array of work orders with wafers available to schedule.
//----------------------------------------------------------------------------------------------------------------------
Service GetAvailableWorkOrders(ReactorType, SusceptorSize, ReactorNo)
	
	WorkOrders = ''
	If ( (ReactorType NE '') or (ReactorNo NE '') ) then
		If ReactorType EQ '' then ReactorType = Xlate('REACTOR', ReactorNo, 'REACT_TYPE', 'X')
	end
	If ReactorType _EQC 'ASM+' then ReactorType = 'ASM'
	WorkOrderList = Database_Services('ReadDataRow', 'APP_INFO', 'SCHED_WO_LIST')
	NumRows       = DCount(WorkOrderList, @FM)         
	For RowIndex = 1 to NumRows
		WOLogKeyID      = WorkOrderList<RowIndex, 1>
		WOLogRow        = Database_Services('ReadDataRow', 'WO_LOG', WOLogKeyID)
		EpiPartNo       = WOLogRow<WO_LOG_EPI_PART_NO$>
		ProdVerNo       = WOLogRow<WO_LOG_PROD_VER_NO$>
		ProdVerRow      = Database_Services('ReadDataRow', 'PROD_VER', ProdVerNo)
		PSN             = ProdVerRow<PROD_VER_PROC_STEP_PSN$>
		ProdSpecRow     = Database_Services('ReadDataRow', 'PROD_SPEC', PSN)
		BlockedReactors = ProdSpecRow<PROD_SPEC_BLOCKED_REACTS$>
		AcceptWorkOrder = True$ ; // Assume this work order is acceptable for now.
		PSNBlock        = False$
		If EpiPartNo NE '' then
			If SusceptorSize NE '' then
				EpiPart         = Database_Services('ReadDataRow', 'EPI_PART', EpiPartNo)
				SuscSize        = EpiPart<EPI_PART_SUB_WAFER_SIZE$>
				If SuscSize NE SusceptorSize then
					// The susceptor size for this work order does not match the required susceptor size.
					AcceptWorkOrder = False$
				end
			end
			If (BlockedReactors NE '') AND (ReactorNo NE '') then
				// This work order uses a part spec that is blocked on the current reactor.
				Locate ReactorNo in BlockedReactors using @VM setting vPos then PSNBlock = True$
			end
			RowReactorType = WorkOrderList<RowIndex, 4>
			If ( (RowReactorType _NEC ReactorType) and (RowReactorType _NEC ReactorType:'+') ) then
				AcceptWorkOrder = False$
			end
			If AcceptWorkOrder then
				Swap False$ with '' in PSNBlock
				Swap True$ with 'Yes' in PSNBlock
				WorkOrderList<RowIndex, 5> = PSNBlock
				WorkOrders                 = Insert(WorkOrders, -1, 0, 0, WorkOrderList<RowIndex>)
			end
		end
	Next RowIndex
	Response    = WorkOrders
	
end service


Service UpdateCassProcStatus(WONo, CassNo, ProcessedDTM)

	ErrorMsg = ''
	If ((WONo NE '') and (CassNo NE '') and (ProcessedDTM NE '') ) then
		If Num(ProcessedDTM) then ProcessedDTM = OConv(ProcessedDTM, 'DT2/^H')
		// Find the SCHED_DET_NG event record that contains the cassette number
		Query  = 'SELECT SCHED_DET_NG WITH WO_NO EQ ':WONo:' AND WITH START_DTM LE "':ProcessedDTM:'" '
		Query := 'AND WITH UNPROCESSED_CASS EQ ':CassNo:' OR WITH PROCESSED_CASS EQ ':CassNo:' BY START_DTM'
		GoSub ClearCursors
		RList(Query, TARGET_ACTIVELIST$, '', '', '')
		EOF  = False$
		Done = False$
		Loop
			Readnext SchedDetNGKey else EOF = True$
		Until EOF
			Schedule_Services('UpdateEventCassProcStatus', WONo, CassNo, SchedDetNGKey)
			If Error_Services('HasError') then ErrorMsg = Error_Services('GetMessage')
			Done = True$
		Until Done
		Repeat
	end else
		ErrorMsg = 'Error in ':Service:' service. WONo, CassNo, and ProcessedDTM cannot be null'
	end
	
	If ErrorMsg NE '' then Error_Services('Add', ErrorMsg)
	
end service


Service UpdateEventCassProcStatus(WONo, CassNo, SchedDetNGKey)
	
	ErrorMsg = ''
	If ( (WONo NE '') and (CassNo NE '') and (SchedDetNGKey NE '') ) then
		SchedDetNGRec = Database_Services('ReadDataRow', 'SCHED_DET_NG', SchedDetNGKey)
		If Error_Services('NoError') then
			RecChanged     = False$
			CassComp       = False$
			UnprocCassList = SchedDetNGRec<SCHED_DET_NG.UNPROCESSED_CASS$>
			ProcCassList   = SchedDetNGRec<SCHED_DET_NG.PROCESSED_CASS$>
			AllSchedCass   = SRP_Array('Join', UnprocCassList, ProcCassList, 'OR', @VM)
			AllSchedCass   = SRP_Array('SortSimpleList', AllSchedCass, 'AscendingNumbers', @VM)				
			ReactType      = Xlate('WO_LOG', WONo, 'REACT_TYPE', 'X')
			If ReactType EQ 'EPP' then
				WMIKey          = WONo:'*1*':CassNo
				If RowExists('WM_IN', WMIKey) then 
					// Since EpiPro splits WM_IN cassettes out into RDS runs, we need to determine if the
					// cassette has been emptied out or if enough wafers have been consumed such that
					// the wafer count equals the split work order event quantity.
					StopDtm         = SchedDetNGRec<SCHED_DET_NG.STOP_DTM$>
					SchedWfrQty     = Schedule_Services('GetScheduledWfrQty', WONo, StopDtm)
					// Determine the cassette and slot this event should end on. This will be used to identify
					// cassettes that should be marked as complete when the event ends in the middle of the cassette.
					// That is, the EpiPro work order events should end on an RDS when they are split around block
					// out events.
					NumSchedCass    = DCount(AllSchedCass, @VM)
					EndCassNo       = AllSchedCass<0, NumSchedCass>
					EndSlotNo       = Mod(SchedWfrQty, 25)
					If EndSlotNo EQ 0 then EndSlotNo = 25
					WMIRdsList      = Xlate('WM_IN', WMIKey, 'RDS_NO', 'X')
					UnloadDtms      = Xlate('RDS', WMIRdsList, 'DATETIME_OUT', 'X')
					NumUnloadedWfrs = 0
					If UnloadDtms NE '' then
						For each UnloadDtm in UnloadDtms using @VM
							NumUnloadedWfrs += (UnloadDtm NE '')
						Next UnloadDtm
					end
					CassRemWfrs = Xlate('WM_IN', WMIKey, 'REM_WFRS', 'X')
					If ( (NumUnloadedWfrs EQ 25) or ( (CassNo EQ EndCassNo) and (NumUnloadedWfrs GE EndSlotNo) ) or (CassRemWfrs EQ 0) ) then
						CassComp = True$
					end
				end
			end else
				WOMatKey       = WoNo:'*':CassNo
				If RowExists('WO_MAT', WOMatKey) then 
					CurrWaferCount = obj_WO_Mat('CurrWaferCnt', WOMatKey)
					RDSNo          = Xlate('WO_MAT', WOMatKey, 'RDS_NO', 'X')
					DateOut        = Xlate('RDS', RDSNo, 'DATE_OUT', 'X')
					CassComp       = ( (CurrWaferCount EQ 0) or (DateOut NE '') )
				end
			end
			
			If CassComp then
				Locate CassNo in UnprocCassList using @VM setting vPos then
					UnprocCassList = Delete(UnprocCassList, 0, vPos, 0)
					RecChanged     = True$
				end
				Locate CassNo in ProcCassList using @VM setting vPos else
					ProcCassList<0, -1> = CassNo
					ProcCassList        = SRP_Array('Clean', ProcCassList, 'TrimAndMakeUnique', @VM)
					ProcCassList        = SRP_Array('SortSimpleList', ProcCassList, 'AscendingNumbers', @VM)
					RecChanged          = True$
				end
			end else
				Locate CassNo in ProcCassList using @VM setting vPos then
					ProcCassList = Delete(ProcCassList, 0, vPos, 0)
					RecChanged   = True$
				end
				Locate CassNo in UnprocCassList using @VM setting vPos else
					UnprocCassList<0, -1> = CassNo
					UnprocCassList        = SRP_Array('Clean', UnprocCassList, 'TrimAndMakeUnique', @VM)
					UnprocCassList        = SRP_Array('SortSimpleList', UnprocCassList, 'AscendingNumbers', @VM)
					RecChanged            = True$
				end
			end
		end else
			ErrorMsg = 'Error in ':Service:' service. Error message: ':Error_Services('GetMessage')
		end
		If RecChanged then			
			SchedDetNGRec<SCHED_DET_NG.UNPROCESSED_CASS$> = UnprocCassList
			SchedDetNGRec<SCHED_DET_NG.PROCESSED_CASS$>   = ProcCassList
			Database_Services('WriteDataRow', 'SCHED_DET_NG', SchedDetNGKey, SchedDetNGRec, True$, False$, True$)
			If Error_Services('HasError') then ErrorMsg = Error_Services('GetMessage')
		end						
	end
	
	If ErrorMsg NE '' then Error_Services('Add', ErrorMsg)
	
end service


Service UpdateEventCassLists(SchedDetNGKey)

	ErrorMsg = ''
	If (SchedDetNGKey NE '') then
		If RowExists('SCHED_DET_NG', SchedDetNGKey) then
			SchedDetNGRec = Database_Services('ReadDataRow', 'SCHED_DET_NG', SchedDetNGKey)
			If Error_Services('NoError') then
				UnprocCassList = SchedDetNGRec<SCHED_DET_NG.UNPROCESSED_CASS$>
				ProcCassList   = SchedDetNGRec<SCHED_DET_NG.PROCESSED_CASS$>
				AllSchedCass   = SRP_Array('Join', UnprocCassList, ProcCassList, 'OR', @VM)
				AllSchedCass   = SRP_Array('SortSimpleList', AllSchedCass, 'AscendingNumbers', @VM)
				WONo           = SchedDetNGRec<SCHED_DET_NG.WO_NO$>
				For each CassNo in AllSchedCass using @VM
					Schedule_Services('UpdateEventCassProcStatus', WONo, CassNo, SchedDetNGKey)
					If Error_Services('HasError') then ErrorMsg = Error_Services('GetMessage')
				Until (ErrorMsg NE '')
				Next CassNo
			end else
				ErrorMsg = 'Error in ':Service:' service. Error message: ':Error_Services('GetMessage')
			end
		end else
			ErrorMsg = 'Error in ':Service:' service. SCHED_DET_NG ':SchedDetNGKey:' does not exist.'
		end
	end else
		ErrorMsg = 'Error in ':Service:' service. SchedDetNGKey cannot be null.'
	end
	
	If ErrorMsg NE '' then Error_Services('Add', ErrorMsg)
	
end service


Service GetEventCurrCass(SchedDetNGKey)
	
	CurrCassNo = ''
	If SchedDetNGKey NE '' then
		SchedDetNGRec = Database_Services('ReadDataRow', 'SCHED_DET_NG', SchedDetNGKey)
		CurrRunNo     = Schedule_Services('GetEventCurrRun', SchedDetNGKey)
		If CurrRunNo NE '' then
			ReactNo   = SchedDetNGRec<SCHED_DET_NG.REACT_NO$>
			WONo      = SchedDetNGRec<SCHED_DET_NG.WO_NO$>
			ReactType = Xlate('REACTOR', ReactNo, 'REACT_TYPE', 'X')
			AllRuns   = Xlate('WO_STEP', WONo:'*1', 'RDS_KEY', 'X')
			CurrRDSNo = AllRuns<0, CurrRunNo>
			If ReactType EQ 'EPP' then
				WMIKeys    = Xlate('RDS', CurrRDSNo, 'WM_IN_KEY', 'X')
				CurrCassNo = WMIKeys[-1, 'B*']
			end else
				CurrCassNo = Xlate('RDS', CurrRDSNo, 'CASS_NO', 'X')
			end
		end else
			// No runs have been loaded yet, so use the first cassette assigned to the event
			UnprocCassList = SchedDetNGRec<SCHED_DET_NG.UNPROCESSED_CASS$>
			ProcCassList   = SchedDetNGRec<SCHED_DET_NG.PROCESSED_CASS$>
			EventCassList  = SRP_Array('Join', ProcCassList, UnprocCassList, 'OR', @VM)
			EventCassList  = SRP_Array('SortSimpleList', EventCassList, 'AscendingNumbers', @VM)
			CurrCassNo     = EventCassList<0, 1>
		end
	end else
		Error_Services('Add', 'Error in ':Service:' service. Null SchedDetNGKey passed in.')
	end
	Response = CurrCassNo
	
end service


Service GetEventCurrRun(SchedDetNGKey)
	
	CurrRunNo = ''
	If SchedDetNGKey NE '' then
		EventRDSKeys = Schedule_Services('GetEventRDSKeys', SchedDetNGKey)
		Swap @VM with @FM in EventRDSKeys
		If EventRDSKeys NE '' then
			Query     = "WITH {DATE_IN} NE ''"
			Tablename = 'RDS'
			Open Tablename to hTable then
				Open 'DICT.RDS' to hDict then
					GoSub ClearCursors
					Cursor   = 0
					Make.List(Cursor, EventRDSKeys, hTable, hDict)
					Mode     = 2 ; // Use existing cursor initialized above
					SortList = '#DATETIME_IN'
					Select Tablename by SortList using Cursor then 
						Reduce(Query, SortList, Mode, Tablename, Cursor, Flag)
						If Flag then
							// Success
							ReadNext RDSKey then CurrRunNo = Xlate('RDS', RDSKey, 'RUN_ORDER_NUM', 'X')
							GoSub ClearCursors
						end
					end
				end
			end
		end
	end else
		Error_Services('Add', 'Error in ':Service:' service. Null SchedDetNGKey passed in.')
	end
	Response = CurrRunNo   
	
end service


Service GetEventRDSKeys(SchedDetNGKey)
	
	RDSKeys    = ''
	If SchedDetNGKey NE '' then
		SchedEventRec = Database_Services('ReadDataRow', 'SCHED_DET_NG', SchedDetNGKey)
		If Error_Services('NoError') then
			WorkOrderNo = SchedEventRec<SCHED_DET_NG.WO_NO$>
			ReactNo     = SchedEventRec<SCHED_DET_NG.REACT_NO$>
			ReactType   = Xlate('REACTOR', ReactNo, 'REACT_TYPE', 'X')
			UnprocCass  = SchedEventRec<SCHED_DET_NG.UNPROCESSED_CASS$>
			ProcCass    = SchedEventRec<SCHED_DET_NG.PROCESSED_CASS$>
			EventComp   = SchedEventRec<SCHED_DET_NG.EVENT_COMP$>
			AllCass     = SRP_Array('Join', UnprocCass, ProcCass, 'OR', @VM)
			AllCass     = SRP_Array('SortSimpleList', AllCass, 'AscendingNumbers', @VM)
			If ReactType NE 'EPP' then                                              
				For each CassNo in AllCass using @VM setting vPos
					WOMatKey       = WorkOrderNo:'*':CassNo
					RDSKeys<0, -1> = Xlate('WO_MAT', WOMatKey, 'RDS_NO', 'X')
				Next CassNo
			end else
				// Determine starting slot to exlude runs that are part of previous schedule events
				QueryDtm     = SchedEventRec<SCHED_DET_NG.START_DTM$>
				SchedQty     = SchedEventRec<SCHED_DET_NG.EVENT_TOTAL_WFRS$>
				EventComp    = SchedEventRec<SCHED_DET_NG.EVENT_COMP$>
				PrevSchedQty = Schedule_Services('GetScheduledWfrQty', WorkOrderNo, QueryDtm)
				StartSlot    = Mod(PrevSchedQty, 25) + 1                        
				TotalQty     = PrevSchedQty + SchedQty
				StopSlot     = Mod(TotalQty, 25)
				If StopSlot EQ 0 then StopSlot = 25
				StartCass    = AllCass<0, 1>
				StopCass     = AllCass[-1, 'B':@VM]
				For each CassNo in AllCass using @VM setting vPos
					WMInKey     = WorkOrderNo:'*1*':CassNo
					WMInRDSList = Xlate('WM_IN', WMInKey, 'RDS_NO', 'X')
					Begin Case
						Case CassNo EQ StartCass
							For SlotIndex = StartSlot to 25
								RDSKeys<0, -1> = WMInRDSList<0, SlotIndex>
							Next SlotIndex
						Case CassNo EQ StopCass
							For SlotIndex = 1 to StopSlot
								RDSKeys<0, -1> = WMInRDSList<0, SlotIndex>
							Next SlotIndex
						Case ( (CassNo EQ StartCass) and (CassNo EQ StopCass) )
							For SlotIndex = StartSlot to StopSlot
								RDSKeys<0, -1> = WMInRDSList<0, SlotIndex>
							Next SlotIndex
						Case Otherwise$
							RDSKeys<0, -1> = WMInRDSList
					End Case
				Next CassNo
			end
			
			If Not(EventComp) then
				// Add in RDS keys not yet loaded (i.e. not associated with any WM_IN record yet).
				Query       = "SELECT RDS WITH WO EQ ":WorkOrderNo:" AND WITH DATE_IN EQ '' BY SEQ"
				RList(Query, TARGET_ACTIVELIST$, '', '', '')
				ErrCode     = ''
				If Not(Get_Status(ErrCode)) then
					EOF = False$
					Loop
						ReadNext RDSKey else EOF = True$
					Until EOF
						RDSKeys<0, -1> = RDSKey
					Repeat
				end
			end
			RDSKeys = SRP_Array('Clean', RDSKeys, 'TrimAndMakeUnique', @VM)            
		end
	end else
		Error_Services('Add', 'Error in ':Service:' service. Null SchedDetNGKey passed in.')
	end
	Response = RDSKeys
	
end service


Service GetEventLoadedRDSKeys(SchedDetNGKey)
	
	RDSKeys    = ''
	If SchedDetNGKey NE '' then
		EventRDSKeys = Schedule_Services('GetEventRDSKeys', SchedDetNGKey)
		Swap @VM with @FM in EventRDSKeys
		If EventRDSKeys NE '' then
			Query     = "WITH {DATE_IN} NE '' AND WITH {DATE_OUT} EQ ''"
			Tablename = 'RDS'
			Open Tablename to hTable then
				Open 'DICT.RDS' to hDict then
					GoSub ClearCursors
					Cursor   = 0
					Make.List(Cursor, EventRDSKeys, hTable, hDict)
					Mode     = 2 ; // Use existing cursor initialized above
					SortList = 'SEQ'
					Reduce(Query, SortList, Mode, Tablename, Cursor, Flag)
					If Flag then
						// Success
						EOF = False$
						Loop
							ReadNext RDSKey else EOF = True$
						Until EOF
							RDSKeys<0, -1> = RDSKey
						Repeat
					end
				end
			end
		end
	end else
		Error_Services('Add', 'Error in ':Service:' service. Null SchedDetNGKey passed in.')
	end
	Response = RDSKeys    
	
end service


Service GetEventLoadedWfrQty(SchedDetNGKey)
	
	LoadedQty = ''
	If SchedDetNGKey NE '' then
		ReactNo   = Xlate('SCHED_DET_NG', SchedDetNGKey, 'REACT_NO', 'X')
		ReactType = Xlate('REACTOR', ReactNo, 'REACT_TYPE', 'X')
		LoadedRDS = Schedule_Services('GetEventLoadedRDSKeys', SchedDetNGKey)
		If LoadedRDS NE '' then
			If ReactType EQ 'EPP' then 
				LoadedQty = Sum(Xlate('REACT_RUN', LoadedRDS, 'WFRS_EPI_LOAD', 'X'))
			end else
				LoadedQty = Sum(Xlate('RDS', LoadedRDS, 'WAFERS_IN', 'X'))
			end
		end
	end else
		Error_Services('Add', 'Error in ':Service:' service. Null SchedDetNGKey passed in.')
	end
	Response = LoadedQty    
	
end service


Service NotifySupervisorsIfSameDayChange(NewRec, OrigRec)
	
	If NewRec NE '' then
		StartDTM = NewRec<SCHED_DET_NG.START_DTM$>
		StopDTM = NewRec<SCHED_DET_NG.STOP_DTM$>
		If (StartDTM NE '') and (StopDTM NE '') then
			CurrentDTM = SRP_DateTime("Now")
			CurrentPlus24DTM = SRP_Datetime("AddHours", CurrentDTM, 24)
			
			IrrelevantStopChange = False$
			IrrelevantStartChange = False$
			If OrigRec NE '' then
				OrigStartDTM = OrigRec<SCHED_DET_NG.START_DTM$>
				OrigStopDTM = OrigRec<SCHED_DET_NG.STOP_DTM$>
				
				If (OrigStartDTM NE '') and (OrigStopDTM NE '') then
					// Check to make sure not just an extension where original stop was already after 24 hours from now
					IrrelevantStopChange = ((OrigStopDTM GE CurrentPlus24DTM) and (StopDTM GE CurrentPlus24DTM)) or (OrigStopDTM EQ StopDTM)
					
					// Check to make sure original start was not already before today and new start still not after now
					IrrelevantStartChange = ((OrigStartDTM LT CurrentDTM) and (StartDTM LT CurrentDTM)) or (OrigStartDTM EQ StartDTM)
				end
			end
			
			If ((IrrelevantStopChange NE True$) or (IrrelevantStartChange NE True$)) and (StartDTM LT StopDTM) then
				SchedEventInNext24Hours = False$
				GoSub VerifySameDayChange
				
				If SchedEventInNext24Hours EQ True$ then
					WorkOrder = NewRec<SCHED_DET_NG.WO_NO$>
					ReactorNo = NewRec<SCHED_DET_NG.REACT_NO$>
					HotLot = Work_Order_Services("GetHotFlag", WorkOrder)
					Description = NewRec<SCHED_DET_NG.DESC$>
					
					Message = ''
					If HotLot NE False$ then Message := '[HOT LOT] '
					Message := 'A same-day schedule change was made'
					If ReactorNo NE '' then
						Message := ' on reactor ':ReactorNo:'.'
					end else
						Message := '.'
					end
					
					EmailSubject = ''
					If HotLot NE False$ then
						EmailSubject = '[HOT LOT] '
					end else
						EmailSubject = ''
					end
					EmailSubject := 'Same-day schedule change!'
					
					GoSub CreateScheduleChangeNotification
				end
			end
		end
	end else
		If OrigRec NE '' then
			StartDTM = OrigRec<SCHED_DET_NG.START_DTM$>
			StopDTM = OrigRec<SCHED_DET_NG.STOP_DTM$>
			If (StartDTM NE '') and (StopDTM NE '') then
				CurrentDTM = SRP_DateTime("Now")
				CurrentPlus24DTM = SRP_Datetime("AddHours", CurrentDTM, 24)
				
				SchedEventInNext24Hours = False$
				GoSub VerifySameDayChange
				
				If SchedEventInNext24Hours EQ True$ then
					WorkOrder = OrigRec<SCHED_DET_NG.WO_NO$>
					ReactorNo = OrigRec<SCHED_DET_NG.REACT_NO$>
					HotLot = Work_Order_Services("GetHotFlag", WorkOrder)
					Description = OrigRec<SCHED_DET_NG.DESC$>
					Message = 'A scheduled event was cancelled or removed on the same day'
					If ReactorNo NE '' then
						Message := ' on reactor ':ReactorNo:'.'
					end else
						Message := '.'
					end
					EmailSubject = 'Same-day schedule change!'
					
					GoSub CreateScheduleChangeNotification
				end	
			end
		end	
	end
	
end service


Service SendRefreshMessage()
	
	SendUpdate     = True$
	LastRefreshDtm = Xlate('APP_INFO', 'AUTO_SCHEDULER_LAST_REFRESH', '', 'X')
	If LastRefreshDtm NE '' then 
		ElapMinutes = SRP_Datetime('MinuteSpan', LastRefreshDtm, Datetime(), True$)
		SendUpdate  = (ElapMinutes GT 5)
	end
	If SendUpdate then
		Messaging_Services('SendMessage', 'RefreshSchedule', 'Response', @User4, 'All', @User4, 'EventHandler', 'NDW_SCHEDULER' : ',OMNIEVENT,@MESSAGE,@ARGUMENTS')
		Database_Services('WriteDataRow', 'APP_INFO', 'AUTO_SCHEDULER_LAST_REFRESH', Datetime(), True$, False$, True$)
	end
	
end service


Service AdjustWorkOrderScheduleQty(WONo, ModifyQty)
    
    ErrorMsg = ''
    Begin Case
        Case (WONo EQ '')
            ErrorMsg = 'Error in ':Service:' service. WONo cannot be null.'
        Case (ModifyQty EQ '')
            ErrorMsg = 'Error in ':Service:' service. ModifyQty cannot be null.'
    End Case
    
    If (ErrorMsg EQ '') then
        
        WOQty   = Xlate('WO_LOG', WONo, 'WO_QTY', 'X')
        OrigQty = WOQty - ModifyQty
        NewQty  = WOQty + ModifyQty
        
        SchedDetNGKey = ''

        Begin Case
            Case ModifyQty GT 0
                
                LogData    = ''
                LogData<1> = OConv(Datetime(), 'DT2/^H')
                LogData<2> = 'WO_LOG_ACTIONS'
                LogData<3> = 'Work order ':WONo:' quantity increased from ':OrigQty:' to ':NewQty:'.'
                Schedule_Services('LogActivity', '', LogData, False$)
                
                LogData<1> = OConv(Datetime(), 'DT2/^H')
                LogData<3> = 'Searching for a scheduled event to increase the quantity.'
                Schedule_Services('LogActivity', '', LogData, False$)
                
                // Search schedule for most recent event and increase that event quantity by the difference.
                Query  = 'SELECT SCHED_DET_NG WITH WO_NO EQ ':WONo:' BY-DSND STOP_DTM'                  
                GoSub ClearCursors
                Set_Status(0)
                RList(Query, TARGET_ACTIVELIST$, '', '', False$)
                errCode       = ''
                If Get_Status(errCode) then
                    
                    ErrorMsg = 'Error retrieving event list in WO_LOG_Actions. RList error code ':errCode:'.'
                    
                    LogData<1> = OConv(Datetime(), 'DT2/^H')
                    LogData<3> = ErrorMsg
                    Schedule_Services('LogActivity', '', LogData, False$)
                    
                    Error_Services('Add', ErrorMsg)
                    
                end else
                    
                    EOF = False$
                    
                    LogData<1> = OConv(Datetime(), 'DT2/^H')
                    LogData<3> = 'Query successful. Number of keys found: ':@RecCount:'.'
                    Schedule_Services('LogActivity', '', LogData, False$)
                    
                    ReadNext SchedDetNGKey else EOF = True$
                    If SchedDetNGKey NE '' then
                        EventRec    = Database_Services('ReadDataRow', 'SCHED_DET_NG', SchedDetNGKey)
                        StopDTM     = EventRec<SCHED_DET_NG.STOP_DTM$>
                        
                        LogData<1> = OConv(Datetime(), 'DT2/^H')
                        LogData<3> = 'Work order record ':WONo:' quantity increased from ':OrigQty:' to ':NewQty:'.'
                        Schedule_Services('LogActivity', EventRec<SCHED_DET_NG.REACT_NO$>, LogData, False$)
                        
                        LogData<1> = OConv(Datetime(), 'DT2/^H')
                        LogData<3> = 'Scheduled event ':SchedDetNGKey:' found.'
                        Schedule_Services('LogActivity', EventRec<SCHED_DET_NG.REACT_NO$>, LogData, False$)
                        
                        // Only modify the event if it is still running or is scheduled in the future.
                        If StopDTM GT Datetime() then
                            
                            LogData<1> = OConv(Datetime(), 'DT2/^H')
                            LogData<3> = 'Event stop DTM ':OConv(StopDtm, 'DT2/^H'):' less than current DTM, so increasing event quantity.'
                            Schedule_Services('LogActivity', EventRec<SCHED_DET_NG.REACT_NO$>, LogData, False$)

                            ReactNo      = EventRec<SCHED_DET_NG.REACT_NO$>
                            WONo         = EventRec<SCHED_DET_NG.WO_NO$>
                            StartDTM     = EventRec<SCHED_DET_NG.START_DTM$>
                            Desc         = EventRec<SCHED_DET_NG.DESC$>
                            EventQty     = EventRec<SCHED_DET_NG.EVENT_TOTAL_WFRS$>
                            NewEventQty  = EventQty + ModifyQty
                            Schedule_Services('ModifySchedEvent', SchedDetNGKey, ReactNo, WONo, StartDTM, StopDTM, Desc, NewEventQty)
                            // Adjust reactor events as needed.
                            Schedule_Services('AdjustScheduleEvents', ReactNo)
                            
                        end else
                            
                            LogData<1> = OConv(Datetime(), 'DT2/^H')
                            LogData<3> = 'Event stop DTM ':OConv(StopDtm, 'DT2/^H'):' greater than current DTM.'
                            Schedule_Services('LogActivity', EventRec<SCHED_DET_NG.REACT_NO$>, LogData, False$)
                            
                            LogData<1> = OConv(Datetime(), 'DT2/^H')
                            LogData<3> = 'Event quantity not increased.'
                            Schedule_Services('LogActivity', EventRec<SCHED_DET_NG.REACT_NO$>, LogData, False$)
                            
                        end
                    end
                end
                GoSub ClearCursors
            Case ModifyQty LT 0
                
                LogData    = ''
                LogData<1> = OConv(Datetime(), 'DT2/^H')
                LogData<2> = 'WO_LOG_ACTIONS'
                LogData<3> = 'Work order ':WONo:' quantity decreased from ':OrigQty:' to ':NewQty:'.'
                Schedule_Services('LogActivity', '', LogData, False$)
                
                LogData<1> = OConv(Datetime(), 'DT2/^H')
                LogData<3> = 'Searching for a scheduled event to decrease the quantity.'
                Schedule_Services('LogActivity', '', LogData, False$)
                
                // Search schedule for most recent events and decrease/delete those events as needed.
                Query  = 'SELECT SCHED_DET_NG WITH WO_NO EQ ':WONo:' BY-DSND STOP_DTM'                  
                GoSub ClearCursors
                Set_Status(0)
                RList(Query, TARGET_ACTIVELIST$, '', '', False$)
                errCode       = ''
                
                If Get_Status(errCode) then
                    
                    ErrorMsg = 'Error retrieving event list in WO_LOG_Actions. RList error code ':errCode:'.'
                    
                    LogData<1> = OConv(Datetime(), 'DT2/^H')
                    LogData<3> = ErrorMsg
                    Schedule_Services('LogActivity', '', LogData, False$)
                    
                    Error_Services('Add', ErrorMsg)
                    
                end else
                    
                    LogData<1> = OConv(Datetime(), 'DT2/^H')
                    LogData<3> = 'Query successful. Number of keys found: ':@RecCount:'.'
                    Schedule_Services('LogActivity', '', LogData, False$)
                    
                    EOF       = False$
                    Done      = False$
                    UpdateReq = False$
                    Loop
                    ReadNext SchedDetNGKey else EOF = True$
                    Until EOF EQ True$
                        If SchedDetNGKey NE '' then
                            EventRec    = Database_Services('ReadDataRow', 'SCHED_DET_NG', SchedDetNGKey)
                            StopDTM   = EventRec<SCHED_DET_NG.STOP_DTM$>
                            
                            LogData<1> = OConv(Datetime(), 'DT2/^H')
                            LogData<3> = 'Work order record ':WONo:' quantity decreased from ':OrigQty:' to ':NewQty:'.'
                            Schedule_Services('LogActivity', EventRec<SCHED_DET_NG.REACT_NO$>, LogData, False$)
                            
                            LogData<1> = OConv(Datetime(), 'DT2/^H')
                            LogData<3> = 'Scheduled event ':SchedDetNGKey:' found.'
                            Schedule_Services('LogActivity', EventRec<SCHED_DET_NG.REACT_NO$>, LogData, False$)
                            
                            // Only modify the event if it is still running or is scheduled in the future.
                            If StopDTM GT Datetime() then
                                
                                LogData<1> = OConv(Datetime(), 'DT2/^H')
                                LogData<3> = 'Event stop DTM ':OConv(StopDtm, 'DT2/^H'):' less than current DTM, so decreasing event quantity.'
                                Schedule_Services('LogActivity', EventRec<SCHED_DET_NG.REACT_NO$>, LogData, False$)
                                
                                UpdateReq = True$
                                ReactNo   = EventRec<SCHED_DET_NG.REACT_NO$>
                                WONo      = EventRec<SCHED_DET_NG.WO_NO$>
                                StartDTM  = EventRec<SCHED_DET_NG.START_DTM$>
                                Desc      = EventRec<SCHED_DET_NG.DESC$>
                                EventQty  = EventRec<SCHED_DET_NG.EVENT_TOTAL_WFRS$>
                                If EventQty GT Abs(ModifyQty) then
                                    
                                    // This event has enough wafers, so just reduce the quantity.
                                    LogData<1> = OConv(Datetime(), 'DT2/^H')
                                    LogData<3> = 'Event quantity ':EventQty:' greater than quantity reduction ':ModifyQty:', so decreasing event quantity.'
                                    Schedule_Services('LogActivity', EventRec<SCHED_DET_NG.REACT_NO$>, LogData, False$)
                                    
                                    Done        = True$
                                    NewEventQty = EventQty + ModifyQty
                                    Schedule_Services('ModifySchedEvent', SchedDetNGKey, ReactNo, WONo, StartDTM, StopDTM, Desc, NewEventQty)
                                    
                                end else
                                    
                                    // This event has fewer wafers than are being removed. Delete this event and continue
                                    // onto the next event.
                                    LogData<1> = OConv(Datetime(), 'DT2/^H')
                                    LogData<3> = 'Event quantity ':EventQty:' less than quantity reduction ':ModifyQty:', so canceling event.'
                                    Schedule_Services('LogActivity', EventRec<SCHED_DET_NG.REACT_NO$>, LogData, False$)
                                    
                                    Schedule_Services('CancelScheduleEvent', SchedDetNGKey, True$)
                                    ModifyQty += EventQty
                                    
                                end
                            end else
                                
                                LogData<1> = OConv(Datetime(), 'DT2/^H')
                                LogData<3> = 'Event stop DTM ':OConv(StopDtm, 'DT2/^H'):' greater than current DTM.'
                                Schedule_Services('LogActivity', EventRec<SCHED_DET_NG.REACT_NO$>, LogData, False$)
                                
                                LogData<1> = OConv(Datetime(), 'DT2/^H')
                                LogData<3> = 'Event quantity not decreased.'
                                Schedule_Services('LogActivity', EventRec<SCHED_DET_NG.REACT_NO$>, LogData, False$)
                                
                            end
                        end
                    Until Done EQ True$
                    Repeat
                    If UpdateReq then
                        // Adjust reactor events as needed.
                        Schedule_Services('AdjustScheduleEvents', ReactNo)
                    end
                end
                GoSub ClearCursors
                
            Case Otherwise$
                Null
        End Case           
        
    end
    
    If ErrorMsg NE '' then Error_Services('Add', ErrorMsg)
    
end service

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

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


LogError:
	
	Message    = Error_Services('GetMessage')
	LogData    = ''
	LogData<1> = Oconv(Date(), 'D4/') : ' ' : Oconv(Time(), 'MTS') ; // Logging DTM
	LogData<2> = Service : ' Error'    ; // Service Step
	LogData<3> = Str(' ', 12) : Message
	Schedule_Services('LogActivity', ReactorNo, LogData)
	
return


VerifySameDayChange:
	
	If (StartDTM LE CurrentDTM) and (StopDTM GE CurrentPlus24DTM) then
		SchedEventInNext24Hours = True$
	end
	If (StartDTM GE CurrentDTM) and (StopDTM LE CurrentPlus24DTM) then
		SchedEventInNext24Hours = True$
	end
	If (StartDTM GE CurrentDTM) and (StartDTM LT CurrentPlus24DTM) and (StopDTM GE CurrentPlus24DTM) then
		SchedEventInNext24Hours = True$
	end
	If (StartDTM LE CurrentDTM) and (StopDTM LE CurrentPlus24DTM) and (StopDTM GT CurrentDTM) then
		SchedEventInNext24Hours = True$
	end
	
return


CreateScheduleChangeNotification:
	
	If WorkOrder NE '' then Message := CRLF$:'Work Order: ':WorkOrder:'.'
	ConvertedStart = OConv(StartDTM, 'DT2/^H')
	If StartDTM NE '' then Message := CRLF$:'Start Time: ':ConvertedStart:'.'
	ConvertedStop = OConv(StopDTM, 'DT2/^H')
	If StopDTM NE '' then Message := CRLF$:'End Time: ':ConvertedStop:'.'
	If Description NE '' then Message := CRLF$:'Description: ':Description:'.'
	
	IncludeNextShift = False$
	//If we are close to shift change we want to add in the next shift.
	CurrTime         = Time()
	if SRP_Time('Hour', CurrTime) EQ 17 OR SRP_Time('Hour', CurrTime) EQ 5 then
		IncludeNextShift = True$
	end
	
	OnShiftSupervisors = Lsl_Users_Services('GetOnShiftUsersByClass', 'Si Shift Supervisors', IncludeNextShift)
	OnShiftLeads = Lsl_Users_Services('GetOnShiftUsersByClass', 'Lead Operator', IncludeNextShift)
	
	Recipients   = OnShiftSupervisors:@VM:OnShiftLeads
	SentFrom     = @User4
	AttachWindow = ''
	AttachKey    = ''
	SendToGroup  = ''
	Parms = Recipients:@RM:SentFrom:@RM:EmailSubject:@RM:Message:@RM:AttachWindow:@RM:AttachKey:@RM:SendToGroup
	obj_Notes('Create',Parms)
	
return

