Function Reactor_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        :   Reactor_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)
        08/10/17    dmb     Original programmer. Adapted from Scheduling_Services. - [IREPIOI-8]
        01/08/18    dmb     Update the CreatePerformanceTrackers service so the CreateWeeklyPerformanceReport is called
                            on Friday mornings for the upcoming week rather than Monday mornings for the current week.
                            - [IREPIOI-14]
        02/17/18    dmb     Add logging to track reactor performance activities. - [IREPIOI-14]
        05/30/18    dmb     Update various services to allow newer reactors to be used for reporting. - [IREPIOI-17]
        06/21/18    dmb     Update the path where templates and reports are stored. - [IREPIOI-33]
        06/26/18    dmb     Add logging to track when this service is being called. - [IREPIOI-56]
        06/26/18    dmb     Update logic on how reactors are selected for reporting. - [IREPIOI-56]
        04/20/21    djs     Added PickPlace to objReactor JSON object in GetReactor service.
        12/06/23    djm     Add MD3 conversion to TargetThickness in metric increment and decrement services.

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

$insert APP_INSERTS
$insert SERVICE_SETUP
$insert REACTOR_EQUATES
$insert RDS_EQUATES
$insert RLIST_EQUATES
$insert TOOL_EQUATES
$Insert REACTOR_LOG_EQUATES
$Insert REACTOR_CHILD_KEY_IDS_EQUATES
$Insert REACTOR_MODES_EQUATES
$Insert REACT_MODE_EQU ;* THESE ARE NOT THE SAME jch, Update 9/15/21. These are the equates for the CONFIG records
$Insert REACT_MODE_NG_EQUATES
$Insert REACT_UTIL_EQU
$Insert SCHED_DET_NG_EQUATES
$Insert REACT_STATUS_EQUATES
$Insert WO_LOG_EQUATES
$Insert REACTOR_DAILY_UPTIME_EQUATES
$Insert REACT_LL_EQUATES
$Insert IQS_VIOL_DATA_EQUATES
$Insert FEATURE_FLAGS_EQUATES
$Insert REACT_PROB_CAT_EQUATES
$Insert NICA_ORDERS_EQUATES
$Insert REACTOR_INJECTOR_SETTINGS_EQUATES
$Insert REACTOR_RATIOS_EQUATES
$Insert REACT_STATE_EQUATES

Equ WOCust$         to 2
Equ SECONDS_IN_DAY$ to 86400

// Uptime Percentage Equates
Equ PRODUCTIVE$          to 1
Equ UNSCHED$             to 2
Equ SCHED$               to 3
Equ NONSCHED$			 to 4
Equ ENG$				 to 5

Declare subroutine  Error_Services, Reactor_Services, Memory_Services, RList, Database_Services, SRP_JSON
Declare subroutine  Excel_Services, Schedule_Services, Logging_Services, Set_Status, obj_React_Status, Errmsg
Declare subroutine  Obj_Notes, Btree.Extract, SRP_Fastarray, Delay, Mona_Services, SRP_List, Msg, Rds_Services
Declare subroutine  React_Assign_Conv, Nica_Orders_Services, obj_React_Mode, Reactor_Log_Services, obj_Post_Log
Declare function    SRP_Array, Reactor_Services, Memory_Services, Database_Services, SRP_Sort_Array, Excel_Services
Declare function    SRP_Math, SRP_Hash, SRP_JSON, Epi_Part_Services, Schedule_Services, Date_Services, Environment_Services
Declare function    Logging_Services, GetCommandLine, NextKey, Reactor_Log_Services, SRP_DateTime, ole_getwebpage
Declare function    Datetime, Reactor_Modes_Services, Work_Order_Services, React_Mode_NG_Services, Lsl_Users_Services
Declare function    SRP_Time, Rds_Services, SRP_Fastarray, Httpclient_Services, SRP_List, Utility, Memberof, Error_Services
Declare function	Nica_Orders_Services, Max, RTI_CreateGUID, GetTickCount

// Report paths for various performance report services.
TemplatesFolder     = Environment_Services('GetApplicationRootPath') : '\Reports\Scheduler\Templates\'
DailyReportsFolder  = Environment_Services('GetApplicationRootPath') : '\Reports\Scheduler\Daily\'
WeeklyReportsFolder = Environment_Services('GetApplicationRootPath') : '\Reports\Scheduler\Weekly\'

CommandLine = GetCommandLine()
Machine     = Environment_Services('GetServer')
LogDate     = Oconv(Date(), 'D4/')
LogTime     = Oconv(Time(), 'MTS')
LoggingDTM  = LogDate : ' ' : LogTime   ; // Logging DTM

IsProd = Environment_Services('IsProd')
If IsProd EQ True$ then
	MonaResource = 'GRP_OPENINSIGHT_MES_OP_FE_REACTORSERVICES'
end else
	MonaResource = 'GRP_OPENINSIGHT_MES_OP_FE_DEV_REACTORSERVICES'
end

GoToService else

end

Return Response else ''

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Service Parameter Options
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Options BOOLEAN       = True$, False$
Options REACTORMETRIC = 'TUBE_BELL_JAR_THK', 'TUBE_BELL_JAR_WFR_CNT', 'SUSC_THK', 'SUSC_WFR_CNT', 'LOWER_QUARTZ_THK', 'LOWER_QUARTZ_WFR', 'ARMS_WFR_CNT', 'EXHAUST_CHAMBER_THK'

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

//----------------------------------------------------------------------------------------------------------------------
// IncrementWfrMetrics
//
// RDSNo. - [Required]
//
// Increment the REACTOR wafer count and thickness when RDS unload is signed.
// 
//----------------------------------------------------------------------------------------------------------------------
Service IncrementWfrMetrics(RDSNo)
	StartTick = GetTickCount()
    MetricName = 'IncrementWfrMetrics'
	
	// Get Reactor and run count details
	RDSRec          = Xlate('RDS', RDSNo, '', 'X', '')
	TargetThickness = Xlate('RDS', RDSNo, 'THICK_TARGET_TOT', 'X', '')
	TargetThickness = OConv(TargetThickness, 'MD3')
	WaferCount      = RDSRec<RDS_WAFERS_IN$>
	ReactorNo       = RDSRec<RDS_REACTOR$>
	OldReactorRec   = Database_Services('ReadDataRow', 'REACTOR', ReactorNo)
	NewReactorRec   = OldReactorRec
	ReactorType     = OldReactorRec<REACTOR_REACT_TYPE$>
	SusceptorSize   = OldReactorRec<REACTOR_SUSC_POCKET_SIZE$>
	SusceptorSize   = SusceptorSize[-4,1]
	
	// Get Old Values
	CurrTubeBellJarThk = OldReactorRec<REACTOR_TUBE_BELL_JAR_THK$>
    CurrTubeBellJarCnt = OldReactorRec<REACTOR_TUBE_BELL_JAR_WFR_CNT$>
    CurrSuscThk        = OldReactorRec<REACTOR_SUSC_THK$>
    CurrSuscWfrCnt     = OldReactorRec<REACTOR_SUSC_WFR_CNT$>
    CurrLowerQuartzThk = OldReactorRec<REACTOR_LOWER_QUARTZ_THK$>
    CurrLowerQuartzWfr = OldReactorRec<REACTOR_LOWER_QUARTZ_WFR$>
    CurrExhaustThk     = OldReactorRec<REACTOR_EXHAUST_CHAMBER_THK$>
    
    // Arms are not used for EPP
	If ReactorType NE  'EPP' then
		CurrArmsWfrCnt = OldReactorRec<REACTOR_ARMS_WFR_CNT$>
		If CurrArmsWfrCnt = '' then CurrArmsWfrCnt = 0
	end
	
	// Set values to 0 if blank
	If CurrTubeBellJarThk = '' then CurrTubeBellJarThk = 0
	If CurrTubeBellJarCnt = '' then CurrTubeBellJarCnt = 0
	If CurrSuscThk  	  = '' then CurrSuscThk = 0
	If CurrSuscWfrCnt     = '' then CurrSuscWfrCnt = 0
	If CurrLowerQuartzThk = '' then CurrLowerQuartzThk = 0
	If CurrLowerQuartzWfr = '' then CurrLowerQuartzWfr = 0
	If CurrExhaustThk     = '' then CurrExhaustThk = 0
	
	Begin Case
	    
	    Case ReactorType = 'EPP'
	        
	        NewReactorRec<REACTOR_TUBE_BELL_JAR_THK$>     = CurrTubeBellJarThk + TargetThickness
            NewReactorRec<REACTOR_TUBE_BELL_JAR_WFR_CNT$> = CurrTubeBellJarCnt + 1
            NewReactorRec<REACTOR_SUSC_THK$>              = CurrSuscThk + TargetThickness
            NewReactorRec<REACTOR_SUSC_WFR_CNT$>          = CurrSuscWfrCnt + 1
            NewReactorRec<REACTOR_LOWER_QUARTZ_THK$>      = CurrLowerQuartzThk + TargetThickness
            NewReactorRec<REACTOR_LOWER_QUARTZ_WFR$>      = CurrLowerQuartzWfr + 1
            NewReactorRec<REACTOR_EXHAUST_CHAMBER_THK$>   = CurrExhaustThk + TargetThickness
	        
	    Case ReactorType = 'HTR'
	        
	        Begin Case
	            
	            Case SusceptorSize = 6
	                CntAddition                                   = (WaferCount / 5)
	                ThkAddition                                   = (WaferCount / 5) * TargetThickness
	                NewReactorRec<REACTOR_TUBE_BELL_JAR_THK$>     = CurrTubeBellJarThk + ThkAddition
                    NewReactorRec<REACTOR_TUBE_BELL_JAR_WFR_CNT$> = CurrTubeBellJarCnt + CntAddition
                    NewReactorRec<REACTOR_SUSC_THK$>              = CurrSuscThk + ThkAddition
                    NewReactorRec<REACTOR_SUSC_WFR_CNT$>          = CurrSuscWfrCnt + CntAddition
                    NewReactorRec<REACTOR_LOWER_QUARTZ_THK$>      = CurrLowerQuartzThk + ThkAddition
                    NewReactorRec<REACTOR_LOWER_QUARTZ_WFR$>      = CurrLowerQuartzWfr + CntAddition
                    NewReactorRec<REACTOR_ARMS_WFR_CNT$>          = CurrArmsWfrCnt + WaferCount
                    NewReactorRec<REACTOR_EXHAUST_CHAMBER_THK$>   = CurrExhaustThk + ThkAddition
                    
                Case SusceptorSize = 8
                    // Round up
	                CntAddition = (WaferCount / 3)
	                If Count(CntAddition, '.') EQ 1 Then
	                    CntAddition = Int(CntAddition) + 1
	                end
	                
	                ThkAddition                                   = (WaferCount / 3) * TargetThickness
	                NewReactorRec<REACTOR_TUBE_BELL_JAR_THK$>     = CurrTubeBellJarThk + ThkAddition
                    NewReactorRec<REACTOR_TUBE_BELL_JAR_WFR_CNT$> = CurrTubeBellJarCnt + CntAddition
                    NewReactorRec<REACTOR_SUSC_THK$>              = CurrSuscThk + ThkAddition
                    NewReactorRec<REACTOR_SUSC_WFR_CNT$>          = CurrSuscWfrCnt + CntAddition
                    NewReactorRec<REACTOR_LOWER_QUARTZ_THK$>      = CurrLowerQuartzThk + ThkAddition
                    NewReactorRec<REACTOR_LOWER_QUARTZ_WFR$>      = CurrLowerQuartzWfr + CntAddition
                    NewReactorRec<REACTOR_ARMS_WFR_CNT$>          = CurrArmsWfrCnt + WaferCount
                    NewReactorRec<REACTOR_EXHAUST_CHAMBER_THK$>   = CurrExhaustThk + ThkAddition
                    
	        End Case
	     	
	    Case ReactorType = 'ASM' OR ReactorType = 'ASM+'
	        
	        ThkAddition = WaferCount * TargetThickness
	        
	        NewReactorRec<REACTOR_TUBE_BELL_JAR_THK$>     = CurrTubeBellJarThk + ThkAddition
            NewReactorRec<REACTOR_TUBE_BELL_JAR_WFR_CNT$> = CurrTubeBellJarCnt + WaferCount
            NewReactorRec<REACTOR_SUSC_THK$>              = CurrSuscThk + ThkAddition
            NewReactorRec<REACTOR_SUSC_WFR_CNT$>          = CurrSuscWfrCnt + WaferCount
            NewReactorRec<REACTOR_LOWER_QUARTZ_THK$>      = CurrLowerQuartzThk + ThkAddition
            NewReactorRec<REACTOR_LOWER_QUARTZ_WFR$>      = CurrLowerQuartzWfr + WaferCount
	        NewReactorRec<REACTOR_ARMS_WFR_CNT$>          = CurrArmsWfrCnt + WaferCount
	        NewReactorRec<REACTOR_EXHAUST_CHAMBER_THK$>   = CurrExhaustThk + ThkAddition
	    
	End Case
	
	Database_Services('WriteDataRow', 'REACTOR', ReactorNo, NewReactorRec, 1, 0, 0)
	
	EndTick = GetTickCount()
    Mona_Services('QueueLatencyAndCountMetrics', MonaResource, MetricName, StartTick, EndTick)
end service


Service GetWaferCntMetricsByReactorID(ReactorID)
	
	If ReactorID NE '' then
		If RowExists('REACTOR', ReactorID) then
			ReactorRec   = Database_Services('ReadDataRow', 'REACTOR', ReactorID)
			ReactorType  = ReactorRec<REACTOR_REACT_TYPE$>
			ResponseData = ''
			Begin Case
				Case ReactorType EQ 'ASM' OR ReactorType EQ 'ASM+'
					ResponseData<1, 1> = ReactorID
					ResponseData<1, 2> = ReactorRec<REACTOR_SUSC_WFR_CNT$>
					ResponseData<1, 3> = ReactorRec<REACTOR_SUSC_THK$>
					ResponseData<1, 4> = ReactorRec<REACTOR_TUBE_BELL_JAR_WFR_CNT$>
					ResponseData<1, 5> = ReactorRec<REACTOR_TUBE_BELL_JAR_THK$>
					ResponseData<1, 6> = ReactorRec<REACTOR_ARMS_WFR_CNT$>
					ResponseData<1, 7> = ReactorRec<REACTOR_EXHAUST_CHAMBER_THK$>
					//These are the column titles to help us with API Responses
					ResponseData<2, 1> = 'ReactorID'
					ResponseData<2, 2> = 'SusceptorWaferCount'
					ResponseData<2, 3> = 'SusceptorThickness'
					ResponseData<2, 4> = 'TubeWaferCount'
					ResponseData<2, 5> = 'TubeThickness'
					ResponseData<2, 6> = 'ArmsWaferCount'
					ResponseData<2, 7> = 'ExhuastChamberThickness'
				Case ReactorType EQ 'HTR'
					ResponseData<1, 1> = ReactorID
					ResponseData<1, 2> = ReactorRec<REACTOR_SUSC_WFR_CNT$>
					ResponseData<1, 3> = ReactorRec<REACTOR_SUSC_THK$>
					ResponseData<1, 4> = ReactorRec<REACTOR_TUBE_BELL_JAR_WFR_CNT$>
					ResponseData<1, 5> = ReactorRec<REACTOR_TUBE_BELL_JAR_THK$>
					ResponseData<1, 6> = ReactorRec<REACTOR_ARMS_WFR_CNT$>
					ResponseData<1, 7> = ReactorRec<REACTOR_EXHAUST_CHAMBER_THK$>
					//These are the column titles to help us with API Responses
					ResponseData<2, 1> = 'ReactorID'
					ResponseData<2, 2> = 'SusceptorWaferCount'
					ResponseData<2, 3> = 'SusceptorThickness'
					ResponseData<2, 4> = 'TubeWaferCount'
					ResponseData<2, 5> = 'TubeThickness'
					ResponseData<2, 6> = 'ArmsWaferCount'
					ResponseData<2, 7> = 'ExhuastChamberThickness'
				Case ReactorType EQ 'EPP'
					ResponseData<1, 1> = ReactorID
					ResponseData<1, 2> = ReactorRec<REACTOR_SUSC_WFR_CNT$>
					ResponseData<1, 3> = ReactorRec<REACTOR_SUSC_THK$>
					ResponseData<1, 4> = ReactorRec<REACTOR_TUBE_BELL_JAR_WFR_CNT$>
					ResponseData<1, 5> = ReactorRec<REACTOR_TUBE_BELL_JAR_THK$>
					ResponseData<1, 6> = ReactorRec<REACTOR_LOWER_QUARTZ_WFR$>
					ResponseData<1, 7> = ReactorRec<REACTOR_LOWER_QUARTZ_THK$>
					ResponseData<1, 8> = ReactorRec<REACTOR_EXHAUST_CHAMBER_THK$>
					//These are the column titles to help us with API Responses
					ResponseData<2, 1> = 'ReactorID'
					ResponseData<2, 2> = 'SusceptorWaferCount'
					ResponseData<2, 3> = 'SusceptorThickness'
					ResponseData<2, 4> = 'BellJarWaferCount'
					ResponseData<2, 5> = 'BellJarThickness'
					ResponseData<2, 6> = 'LowerQuartzWaferCount'
					ResponseData<2, 7> = 'LowerQuartzThickness'
					ResponseData<2, 8> = 'ExhuastChamberThickness'
			End Case
			Response = ResponseData
		end else
			Error_Services('Add', 'Error: Invalid Reactor ID passed to REACTOR_SERVICES("GetWaferMetricsByReactorID"')
		end
	end else
		Error_Services('Add', 'Error: Null Reactor ID passed to REACTOR_SERVICES("GetWaferMetricsByReactorID"')
	end
	
end service


//----------------------------------------------------------------------------------------------------------------------
// DecrementWfrMetrics
//
// RDSNo. - [Required]
//
// Decrement the REACTOR wafer count and thickness when RDS unload is unsigned.
// 
//----------------------------------------------------------------------------------------------------------------------
Service DecrementWfrMetrics(RDSNo)
	
	// Get Reactor and run count details
	RDSRec          = Xlate('RDS', RDSNo, '', 'X', '')
	TargetThickness = Xlate('RDS', RDSNo, 'THICK_TARGET_TOT', 'X', '')
	TargetThickness = OConv(TargetThickness, 'MD3')
	WaferCount      = RDSRec<RDS_WAFERS_IN$>
	ReactorNo       = RDSRec<RDS_REACTOR$>
	OldReactorRec   = Database_Services('ReadDataRow', 'REACTOR', ReactorNo)
	NewReactorRec   = OldReactorRec
	ReactorType     = OldReactorRec<REACTOR_REACT_TYPE$>
	SusceptorSize   = OldReactorRec<REACTOR_SUSC_POCKET_SIZE$>
	SusceptorSize   = SusceptorSize[-4,1]
	
	// Get Old Values
	CurrTubeBellJarThk = OldReactorRec<REACTOR_TUBE_BELL_JAR_THK$>
    CurrTubeBellJarCnt = OldReactorRec<REACTOR_TUBE_BELL_JAR_WFR_CNT$>
    CurrSuscThk        = OldReactorRec<REACTOR_SUSC_THK$>
    CurrSuscWfrCnt     = OldReactorRec<REACTOR_SUSC_WFR_CNT$>
    CurrLowerQuartzThk = OldReactorRec<REACTOR_LOWER_QUARTZ_THK$>
    CurrLowerQuartzWfr = OldReactorRec<REACTOR_LOWER_QUARTZ_WFR$>
    CurrExhaustThk     = OldReactorRec<REACTOR_EXHAUST_CHAMBER_THK$>
    
    // Arms are not used for EPP
	If ReactorType NE  'EPP' then
		CurrArmsWfrCnt = OldReactorRec<REACTOR_ARMS_WFR_CNT$>
		If CurrArmsWfrCnt = '' then CurrArmsWfrCnt = 0
	end
	
	// Set values to 0 if blank
	If CurrTubeBellJarThk  = '' then CurrTubeBellJarThk = 0
	If CurrTubeBellJarCnt = '' then CurrTubeBellJarCnt = 0
	If CurrSuscThk = '' then CurrSuscThk = 0
	If CurrSuscWfrCnt = '' then CurrSuscWfrCnt = 0
	If CurrLowerQuartzThk = '' then CurrLowerQuartzThk = 0
	If CurrLowerQuartzWfr = '' then CurrLowerQuartzWfr = 0
	If CurrExhaustThk     = '' then CurrExhaustThk = 0
	
	Begin Case
	    
	    Case ReactorType = 'EPP'
	        
	        NewReactorRec<REACTOR_TUBE_BELL_JAR_THK$>     = CurrTubeBellJarThk - TargetThickness
            NewReactorRec<REACTOR_TUBE_BELL_JAR_WFR_CNT$> = CurrTubeBellJarCnt - 1
            NewReactorRec<REACTOR_SUSC_THK$>              = CurrSuscThk - TargetThickness
            NewReactorRec<REACTOR_SUSC_WFR_CNT$>          = CurrSuscWfrCnt - 1
            NewReactorRec<REACTOR_LOWER_QUARTZ_THK$>      = CurrLowerQuartzThk - TargetThickness
            NewReactorRec<REACTOR_LOWER_QUARTZ_WFR$>      = CurrLowerQuartzWfr - 1
            NewReactorRec<REACTOR_EXHAUST_CHAMBER_THK$>   = CurrExhaustThk - TargetThickness
	        
	    Case ReactorType = 'HTR'
	        
	        Begin Case
	            
	            Case SusceptorSize = 6
	                CntAddition                                   = (WaferCount / 5)
	                ThkAddition                                   = (WaferCount / 5) * TargetThickness
	                NewReactorRec<REACTOR_TUBE_BELL_JAR_THK$>     = CurrTubeBellJarThk - ThkAddition
                    NewReactorRec<REACTOR_TUBE_BELL_JAR_WFR_CNT$> = CurrTubeBellJarCnt - CntAddition
                    NewReactorRec<REACTOR_SUSC_THK$>              = CurrSuscThk - ThkAddition
                    NewReactorRec<REACTOR_SUSC_WFR_CNT$>          = CurrSuscWfrCnt - CntAddition
                    NewReactorRec<REACTOR_LOWER_QUARTZ_THK$>      = CurrLowerQuartzThk - ThkAddition
                    NewReactorRec<REACTOR_LOWER_QUARTZ_WFR$>      = CurrLowerQuartzWfr - CntAddition
                    NewReactorRec<REACTOR_ARMS_WFR_CNT$>          = CurrArmsWfrCnt - WaferCount
                    NewReactorRec<REACTOR_EXHAUST_CHAMBER_THK$>   = CurrExhaustThk - ThkAddition
                    
                Case SusceptorSize = 8
                    // Round up
	                CntAddition = (WaferCount / 3)
	                If Count(CntAddition, '.') EQ 1 Then
	                    CntAddition = Int(CntAddition) + 1
	                end
	                
	                ThkAddition                                   = (WaferCount / 3) * TargetThickness
	                NewReactorRec<REACTOR_TUBE_BELL_JAR_THK$>     = CurrTubeBellJarThk - ThkAddition
                    NewReactorRec<REACTOR_TUBE_BELL_JAR_WFR_CNT$> = CurrTubeBellJarCnt - CntAddition
                    NewReactorRec<REACTOR_SUSC_THK$>              = CurrSuscThk - ThkAddition
                    NewReactorRec<REACTOR_SUSC_WFR_CNT$>          = CurrSuscWfrCnt - CntAddition
                    NewReactorRec<REACTOR_LOWER_QUARTZ_THK$>      = CurrLowerQuartzThk - ThkAddition
                    NewReactorRec<REACTOR_LOWER_QUARTZ_WFR$>      = CurrLowerQuartzWfr - CntAddition
                    NewReactorRec<REACTOR_ARMS_WFR_CNT$>          = CurrArmsWfrCnt - WaferCount
                    NewReactorRec<REACTOR_EXHAUST_CHAMBER_THK$>   = CurrExhaustThk - ThkAddition
                    
	        End Case
	     	
	    Case ReactorType = 'ASM' OR ReactorType = 'ASM+'
	        
	        ThkAddition = WaferCount * TargetThickness
	        
	        NewReactorRec<REACTOR_TUBE_BELL_JAR_THK$>     = CurrTubeBellJarThk - ThkAddition
            NewReactorRec<REACTOR_TUBE_BELL_JAR_WFR_CNT$> = CurrTubeBellJarCnt - WaferCount
            NewReactorRec<REACTOR_SUSC_THK$>              = CurrSuscThk - ThkAddition
            NewReactorRec<REACTOR_SUSC_WFR_CNT$>          = CurrSuscWfrCnt - WaferCount
            NewReactorRec<REACTOR_LOWER_QUARTZ_THK$>      = CurrLowerQuartzThk - ThkAddition
            NewReactorRec<REACTOR_LOWER_QUARTZ_WFR$>      = CurrLowerQuartzWfr - WaferCount
	        NewReactorRec<REACTOR_ARMS_WFR_CNT$>          = CurrArmsWfrCnt - WaferCount
	        NewReactorRec<REACTOR_EXHAUST_CHAMBER_THK$>   = CurrExhaustThk - ThkAddition
	    
	End Case
	
	Database_Services('WriteDataRow', 'REACTOR', ReactorNo, NewReactorRec, 1, 0, 0)
	
end service


//----------------------------------------------------------------------------------------------------------------------
// IncrementTestWfrMetrics
//
// TargetThickness - [Required]
// WaferCount - [Required]
// ReactorNo - [Required]
//
// Increment the REACTOR wafer count and thickness for test wafers.
// 
//----------------------------------------------------------------------------------------------------------------------
Service IncrementTestWfrMetrics(TargetThickness, WaferCount, ReactorNo)
	
	OldReactorRec = Database_Services('ReadDataRow', 'REACTOR', ReactorNo)
	NewReactorRec = OldReactorRec
	ReactorType   = OldReactorRec<REACTOR_REACT_TYPE$>
	SusceptorSize = OldReactorRec<REACTOR_SUSC_POCKET_SIZE$>
	SusceptorSize = SusceptorSize[-4,1]
	
	// Get Old Values
	CurrTubeBellJarThk = OldReactorRec<REACTOR_TUBE_BELL_JAR_THK$>
    CurrTubeBellJarCnt = OldReactorRec<REACTOR_TUBE_BELL_JAR_WFR_CNT$>
    CurrSuscThk        = OldReactorRec<REACTOR_SUSC_THK$>
    CurrSuscWfrCnt     = OldReactorRec<REACTOR_SUSC_WFR_CNT$>
    CurrLowerQuartzThk = OldReactorRec<REACTOR_LOWER_QUARTZ_THK$>
    CurrLowerQuartzWfr = OldReactorRec<REACTOR_LOWER_QUARTZ_WFR$>
    CurrExhaustThk     = OldReactorRec<REACTOR_EXHAUST_CHAMBER_THK$>
    
    // Arms are not used for EPP
	If ReactorType NE  'EPP' then
		CurrArmsWfrCnt = OldReactorRec<REACTOR_ARMS_WFR_CNT$>
		If CurrArmsWfrCnt = '' then CurrArmsWfrCnt = 0
	end
	
	// Set values to 0 if blank
	If CurrTubeBellJarThk = '' then CurrTubeBellJarThk = 0
	If CurrTubeBellJarCnt = '' then CurrTubeBellJarCnt = 0
	If CurrSuscThk  	  = '' then CurrSuscThk = 0
	If CurrSuscWfrCnt     = '' then CurrSuscWfrCnt = 0
	If CurrLowerQuartzThk = '' then CurrLowerQuartzThk = 0
	If CurrLowerQuartzWfr = '' then CurrLowerQuartzWfr = 0
	
	Begin Case
	    
	    Case ReactorType = 'EPP'
	        
	        NewReactorRec<REACTOR_TUBE_BELL_JAR_THK$>     = CurrTubeBellJarThk + TargetThickness
            NewReactorRec<REACTOR_TUBE_BELL_JAR_WFR_CNT$> = CurrTubeBellJarCnt + 1
            NewReactorRec<REACTOR_SUSC_THK$>              = CurrSuscThk + TargetThickness
            NewReactorRec<REACTOR_SUSC_WFR_CNT$>          = CurrSuscWfrCnt + 1
            NewReactorRec<REACTOR_LOWER_QUARTZ_THK$>      = CurrLowerQuartzThk + TargetThickness
            NewReactorRec<REACTOR_LOWER_QUARTZ_WFR$>      = CurrLowerQuartzWfr + 1
            NewReactorRec<REACTOR_EXHAUST_CHAMBER_THK$>   = CurrExhaustThk + TargetThickness
	        
	    Case ReactorType = 'HTR'
	        
	        Begin Case
	            
	            Case SusceptorSize = 6
	                CntAddition                                   = (WaferCount / 5)
	                ThkAddition                                   = (WaferCount / 5) * TargetThickness
	                NewReactorRec<REACTOR_TUBE_BELL_JAR_THK$>     = CurrTubeBellJarThk + ThkAddition
                    NewReactorRec<REACTOR_TUBE_BELL_JAR_WFR_CNT$> = CurrTubeBellJarCnt + CntAddition
                    NewReactorRec<REACTOR_SUSC_THK$>              = CurrSuscThk + ThkAddition
                    NewReactorRec<REACTOR_SUSC_WFR_CNT$>          = CurrSuscWfrCnt + CntAddition
                    NewReactorRec<REACTOR_LOWER_QUARTZ_THK$>      = CurrLowerQuartzThk + ThkAddition
                    NewReactorRec<REACTOR_LOWER_QUARTZ_WFR$>      = CurrLowerQuartzWfr + CntAddition
                    NewReactorRec<REACTOR_ARMS_WFR_CNT$>          = CurrArmsWfrCnt + WaferCount
                    NewReactorRec<REACTOR_EXHAUST_CHAMBER_THK$>   = CurrExhaustThk + ThkAddition
                    
                Case SusceptorSize = 8
                    // Round up
	                CntAddition = (WaferCount / 3)
	                If Count(CntAddition, '.') EQ 1 Then
	                    CntAddition = Int(CntAddition) + 1
	                end
	                
	                ThkAddition                                   = (WaferCount / 3) * TargetThickness
	                NewReactorRec<REACTOR_TUBE_BELL_JAR_THK$>     = CurrTubeBellJarThk + ThkAddition
                    NewReactorRec<REACTOR_TUBE_BELL_JAR_WFR_CNT$> = CurrTubeBellJarCnt + CntAddition
                    NewReactorRec<REACTOR_SUSC_THK$>              = CurrSuscThk + ThkAddition
                    NewReactorRec<REACTOR_SUSC_WFR_CNT$>          = CurrSuscWfrCnt + CntAddition
                    NewReactorRec<REACTOR_LOWER_QUARTZ_THK$>      = CurrLowerQuartzThk + ThkAddition
                    NewReactorRec<REACTOR_LOWER_QUARTZ_WFR$>      = CurrLowerQuartzWfr + CntAddition
                    NewReactorRec<REACTOR_ARMS_WFR_CNT$>          = CurrArmsWfrCnt + WaferCount
                    NewReactorRec<REACTOR_EXHAUST_CHAMBER_THK$>   = CurrExhaustThk + ThkAddition
                    
	        End Case
	     	
	    Case ReactorType = 'ASM' OR ReactorType = 'ASM+'
	        
	        ThkAddition = WaferCount * TargetThickness
	        
	        NewReactorRec<REACTOR_TUBE_BELL_JAR_THK$>     = CurrTubeBellJarThk + ThkAddition
            NewReactorRec<REACTOR_TUBE_BELL_JAR_WFR_CNT$> = CurrTubeBellJarCnt + WaferCount
            NewReactorRec<REACTOR_SUSC_THK$>              = CurrSuscThk + ThkAddition
            NewReactorRec<REACTOR_SUSC_WFR_CNT$>          = CurrSuscWfrCnt + WaferCount
            NewReactorRec<REACTOR_LOWER_QUARTZ_THK$>      = CurrLowerQuartzThk + ThkAddition
            NewReactorRec<REACTOR_LOWER_QUARTZ_WFR$>      = CurrLowerQuartzWfr + WaferCount
	        NewReactorRec<REACTOR_ARMS_WFR_CNT$>          = CurrArmsWfrCnt + WaferCount
	        NewReactorRec<REACTOR_EXHAUST_CHAMBER_THK$>   = CurrExhaustThk + ThkAddition
	    
	End Case
	
	Database_Services('WriteDataRow', 'REACTOR', ReactorNo, NewReactorRec, 1, 0, 0)
	
end service


//----------------------------------------------------------------------------------------------------------------------
// DecrementTestWfrMetrics
//
// TargetThickness - [Required]
// WaferCount - [Required]
// ReactorNo - [Required]
//
// Decrement the REACTOR wafer count and thickness for test wafers.
// 
//----------------------------------------------------------------------------------------------------------------------
Service DecrementTestWfrMetrics(TargetThickness, WaferCount, ReactorNo)
	
	OldReactorRec = Database_Services('ReadDataRow', 'REACTOR', ReactorNo)
	NewReactorRec = OldReactorRec
	ReactorType   = OldReactorRec<REACTOR_REACT_TYPE$>
	SusceptorSize = OldReactorRec<REACTOR_SUSC_POCKET_SIZE$>
	SusceptorSize = SusceptorSize[-4,1]
	
	// Get Old Values
	CurrTubeBellJarThk = OldReactorRec<REACTOR_TUBE_BELL_JAR_THK$>
    CurrTubeBellJarCnt = OldReactorRec<REACTOR_TUBE_BELL_JAR_WFR_CNT$>
    CurrSuscThk        = OldReactorRec<REACTOR_SUSC_THK$>
    CurrSuscWfrCnt     = OldReactorRec<REACTOR_SUSC_WFR_CNT$>
    CurrLowerQuartzThk = OldReactorRec<REACTOR_LOWER_QUARTZ_THK$>
    CurrLowerQuartzWfr = OldReactorRec<REACTOR_LOWER_QUARTZ_WFR$>
    CurrExhaustThk     = OldReactorRec<REACTOR_EXHAUST_CHAMBER_THK$>
    
    // Arms are not used for EPP
	If ReactorType NE  'EPP' then
		CurrArmsWfrCnt = OldReactorRec<REACTOR_ARMS_WFR_CNT$>
		If CurrArmsWfrCnt = '' then CurrArmsWfrCnt = 0
	end
	
	// Set values to 0 if blank
	If CurrTubeBellJarThk  = '' then CurrTubeBellJarThk = 0
	If CurrTubeBellJarCnt = '' then CurrTubeBellJarCnt = 0
	If CurrSuscThk = '' then CurrSuscThk = 0
	If CurrSuscWfrCnt = '' then CurrSuscWfrCnt = 0
	If CurrLowerQuartzThk = '' then CurrLowerQuartzThk = 0
	If CurrLowerQuartzWfr = '' then CurrLowerQuartzWfr = 0
	If CurrExhaustThk = '' then CurrExhaustThk = 0
	
	Begin Case
	    
	    Case ReactorType = 'EPP'
	        
	        NewReactorRec<REACTOR_TUBE_BELL_JAR_THK$>     = CurrTubeBellJarThk - TargetThickness
            NewReactorRec<REACTOR_TUBE_BELL_JAR_WFR_CNT$> = CurrTubeBellJarCnt - 1
            NewReactorRec<REACTOR_SUSC_THK$>              = CurrSuscThk - TargetThickness
            NewReactorRec<REACTOR_SUSC_WFR_CNT$>          = CurrSuscWfrCnt - 1
            NewReactorRec<REACTOR_LOWER_QUARTZ_THK$>      = CurrLowerQuartzThk - TargetThickness
            NewReactorRec<REACTOR_LOWER_QUARTZ_WFR$>      = CurrLowerQuartzWfr - 1
            NewReactorRec<REACTOR_EXHAUST_CHAMBER_THK$>   = CurrExhaustThk - TargetThickness
	        
	    Case ReactorType = 'HTR'
	        
	        Begin Case
	            
	            Case SusceptorSize = 6
	                CntAddition                                   = (WaferCount / 5)
	                ThkAddition                                   = (WaferCount / 5) * TargetThickness
	                NewReactorRec<REACTOR_TUBE_BELL_JAR_THK$>     = CurrTubeBellJarThk - ThkAddition
                    NewReactorRec<REACTOR_TUBE_BELL_JAR_WFR_CNT$> = CurrTubeBellJarCnt - CntAddition
                    NewReactorRec<REACTOR_SUSC_THK$>              = CurrSuscThk - ThkAddition
                    NewReactorRec<REACTOR_SUSC_WFR_CNT$>          = CurrSuscWfrCnt - CntAddition
                    NewReactorRec<REACTOR_LOWER_QUARTZ_THK$>      = CurrLowerQuartzThk - ThkAddition
                    NewReactorRec<REACTOR_LOWER_QUARTZ_WFR$>      = CurrLowerQuartzWfr - CntAddition
                    NewReactorRec<REACTOR_ARMS_WFR_CNT$>          = CurrArmsWfrCnt - WaferCount
                    NewReactorRec<REACTOR_EXHAUST_CHAMBER_THK$>   = CurrExhaustThk - ThkAddition
                    
                Case SusceptorSize = 8
                    // Round up
	                CntAddition = (WaferCount / 3)
	                If Count(CntAddition, '.') EQ 1 Then
	                    CntAddition = Int(CntAddition) + 1
	                end
	                
	                ThkAddition                                   = (WaferCount / 3) * TargetThickness
	                NewReactorRec<REACTOR_TUBE_BELL_JAR_THK$>     = CurrTubeBellJarThk - ThkAddition
                    NewReactorRec<REACTOR_TUBE_BELL_JAR_WFR_CNT$> = CurrTubeBellJarCnt - CntAddition
                    NewReactorRec<REACTOR_SUSC_THK$>              = CurrSuscThk - ThkAddition
                    NewReactorRec<REACTOR_SUSC_WFR_CNT$>          = CurrSuscWfrCnt - CntAddition
                    NewReactorRec<REACTOR_LOWER_QUARTZ_THK$>      = CurrLowerQuartzThk - ThkAddition
                    NewReactorRec<REACTOR_LOWER_QUARTZ_WFR$>      = CurrLowerQuartzWfr - CntAddition
                    NewReactorRec<REACTOR_ARMS_WFR_CNT$>          = CurrArmsWfrCnt - WaferCount
                    NewReactorRec<REACTOR_EXHAUST_CHAMBER_THK$>   = CurrExhaustThk - ThkAddition
                    
	        End Case
	     	
	    Case ReactorType = 'ASM' OR ReactorType = 'ASM+'
	        
	        ThkAddition = WaferCount * TargetThickness
	        
	        NewReactorRec<REACTOR_TUBE_BELL_JAR_THK$>     = CurrTubeBellJarThk - ThkAddition
            NewReactorRec<REACTOR_TUBE_BELL_JAR_WFR_CNT$> = CurrTubeBellJarCnt - WaferCount
            NewReactorRec<REACTOR_SUSC_THK$>              = CurrSuscThk - ThkAddition
            NewReactorRec<REACTOR_SUSC_WFR_CNT$>          = CurrSuscWfrCnt - WaferCount
            NewReactorRec<REACTOR_LOWER_QUARTZ_THK$>      = CurrLowerQuartzThk - ThkAddition
            NewReactorRec<REACTOR_LOWER_QUARTZ_WFR$>      = CurrLowerQuartzWfr - WaferCount
	        NewReactorRec<REACTOR_ARMS_WFR_CNT$>          = CurrArmsWfrCnt - WaferCount
	        NewReactorRec<REACTOR_EXHAUST_CHAMBER_THK$>   = CurrExhaustThk - ThkAddition
	    
	End Case
	
	Database_Services('WriteDataRow', 'REACTOR', ReactorNo, NewReactorRec, 1, 0, 0)
	
end service



//----------------------------------------------------------------------------------------------------------------------
// ResetWfrMetric
//
// RDSNo. - [Required]
// MetricType - [Required]('TUBE_BELL_JAR_THK', 'TUBE_BELL_JAR_WFR_CNT', 'SUSC_THK', 'SUSC_WFR_CNT', 'LOWER_QUARTZ_THK', 'LOWER_QUARTZ_WFR', 'ARMS_WFR_CNT', EXHAUST_CHAMBER_THK)
//
// Reset a specified REACTOR metric to 0.
// 
//----------------------------------------------------------------------------------------------------------------------
Service ResetWfrMetric(ReactorNo, MetricType = REACTORMETRIC)
	
	ReactorRec = Database_Services('ReadDataRow', 'REACTOR', ReactorNo)
	
	Begin Case
		
		Case MetricType = 'TUBE_BELL_JAR_THK'
			ReactorRec<REACTOR_TUBE_BELL_JAR_THK$> = 0
			
		Case MetricType = 'TUBE_BELL_JAR_WFR_CNT'
			ReactorRec<REACTOR_TUBE_BELL_JAR_WFR_CNT$> = 0
			
		Case MetricType = 'SUSC_THK'
			ReactorRec<REACTOR_SUSC_THK$> = 0
			
		Case MetricType = 'SUSC_WFR_CNT'
			ReactorRec<REACTOR_SUSC_WFR_CNT$> = 0
			
		Case MetricType = 'LOWER_QUARTZ_THK'
			ReactorRec<REACTOR_LOWER_QUARTZ_THK$> = 0
			
		Case MetricType = 'LOWER_QUARTZ_WFR'
			ReactorRec<REACTOR_LOWER_QUARTZ_WFR$> = 0
			
		Case MetricType = 'ARMS_WFR_CNT'
			ReactorRec<REACTOR_ARMS_WFR_CNT$> = 0
			
		Case MetricType = 'EXHAUST_CHAMBER_THK'
			ReactorRec<REACTOR_EXHAUST_CHAMBER_THK$> = 0
			
	End Case
	
	Database_Services('WriteDataRow', 'REACTOR', ReactorNo, ReactorRec, 1, 0, 0)
	
end service


//----------------------------------------------------------------------------------------------------------------------
// GetReactors
//
// Returns a JSON formatted array of reactor objects. Reactor number 0 will be ommitted. If the MatchType or MatchSize
// arguments are populated, the array will be filterd accordingly.
//----------------------------------------------------------------------------------------------------------------------
Service GetReactors(MatchType, MatchSize)
    
    ServiceKeyID   := '*' : MatchType : '*' : MatchSize
    Reactors        = ''
    
    If Memory_Services('IsValueCurrent', ServiceKeyID, 3600, True$) then
        Reactors    = Memory_Services('GetValue', ServiceKeyID)
    end else
        objReactors = ''
        If SRP_JSON(objReactors, 'NEW', 'OBJECT') then
            objReactorArray = ''
            If SRP_JSON(objReactorArray, 'NEW', 'ARRAY') then
                hReactors   = Database_Services('GetTableHandle', 'REACTOR')
                If Error_Services('NoError') then
                    rv = Set_Status(0)
                    RList('SELECT REACTOR BY REACT_NO', 5, '', '', '')
                    Done        = False$
                    Reactors    = ''
                    Loop
                        Readnext ReactorNo else Done = True$
                    Until Done
                        Reactor     = Reactor_Services('GetReactor', ReactorNo)
                        If SRP_JSON(objReactor, 'PARSE', Reactor) EQ '' then
                            ReactorType = SRP_JSON(objReactor, 'GETVALUE', 'Type')
                            ReactorSize = SRP_JSON(objReactor, 'GETVALUE', 'SusceptorSize')
                            IncludeReactor  = True$ ; // Assume reactor will be included in the response.
                            Begin Case
                                Case ReactorNo EQ 0
                                    // Reactor number 0 is not a real reactor.
                                    IncludeReactor  = False$
                                Case MatchType NE ''
                                    Begin Case
                                        Case MatchType[1, 3] EQ 'ASM' AND Not((ReactorType[1, 3] EQ 'ASM' OR ReactorType EQ 'EPS'))
                                            IncludeReactor  = False$
                                        Case MatchType EQ 'EPS' AND Not((ReactorType[1, 3] EQ 'ASM' OR ReactorType EQ 'EPS'))
                                            IncludeReactor  = False$
                                        Case MatchType EQ 'EPP' AND Not((ReactorType EQ 'EPP' OR ReactorType EQ 'EpiPro'))
                                            IncludeReactor  = False$
                                        Case MatchType EQ 'EpiPro' AND Not((ReactorType EQ 'EPP' OR ReactorType EQ 'EpiPro'))
                                            IncludeReactor  = False$
                                        Case MatchType _EQC 'GAN' AND Not(ReactorType _EQC 'GAN')
                                            IncludeReactor  = False$
                                        Case MatchType NE ReactorType
                                            IncludeReactor  = False$
                                    End Case
                                Case MatchSize NE ''
                                    If MatchSize NE ReactorSize then IncludeReactor  = False$
                                Case Not(RowExists('TOOL', 'R':ReactorNo))
                                    // This tool does not exist
                                    IncludeReactor  = False$
                            End Case
                            If IncludeReactor then
                                SRP_JSON(objReactorArray, 'ADD', objReactor)
                            end
                            SRP_JSON(objReactor, 'RELEASE')
                        end
                    Repeat
                    SRP_JSON(objReactors, 'SET', 'Reactors', objReactorArray)
                    Reactors    = SRP_JSON(objReactors, 'STRINGIFY', 'FAST')
                    Memory_Services('SetValue', ServiceKeyID, Reactors)
                end
                SRP_JSON(objReactorArray, 'RELEASE')
            end else
                Error_Services('Add', 'Error creating objReactorArray in the ' : Service : ' service.')
            end
            SRP_JSON(objReactors, 'RELEASE')
        end else
            Error_Services('Add', 'Error creating objReactors in the ' : Service : ' service.')
        end
    end
    
    Response    = Reactors
    
end service


//----------------------------------------------------------------------------------------------------------------------
// GetReactorNumbers
//
// Returns an @FM delimited list of Reactor Numbers.
//----------------------------------------------------------------------------------------------------------------------
Service GetReactorNumbers(MatchType, MatchSize)
    
    ServiceKeyID   := '*' : MatchType : '*' : MatchSize
    ReactorNumbers  = ''
    
    If Memory_Services('IsValueCurrent', ServiceKeyID, 3600, True$) then
        ReactorNumbers  = Memory_Services('GetValue', ServiceKeyID)
    end else
        hReactors       = Database_Services('GetTableHandle', 'REACTOR')
        If Error_Services('NoError') then
            rv = Set_Status(0)
            RList('SELECT REACTOR BY REACT_NO', 5, '', '', '')
            Done        = False$
            Reactors    = ''
            Loop
                Readnext ReactorNo else Done = True$
            Until Done
                Reactor     = Reactor_Services('GetReactor', ReactorNo)
                If SRP_JSON(objReactor, 'PARSE', Reactor) EQ '' then
                    ReactorType = SRP_JSON(objReactor, 'GETVALUE', 'Type')
                    ReactorSize = SRP_JSON(objReactor, 'GETVALUE', 'SusceptorSize')
                    IncludeReactor  = True$ ; // Assume reactor will be included in the response.
                    Begin Case
                        Case ReactorType EQ ''
                            IncludeReactor = False$
                        Case MatchType NE ''
                            Begin Case
                                Case MatchType[1, 3] EQ 'ASM' AND Not((ReactorType[1, 3] EQ 'ASM' OR ReactorType EQ 'EPS'))
                                    IncludeReactor  = False$
                                Case MatchType EQ 'EPS' AND Not((ReactorType[1, 3] EQ 'ASM' OR ReactorType EQ 'EPS'))
                                    IncludeReactor  = False$
                                Case MatchType EQ 'EPP' AND Not((ReactorType EQ 'EPP' OR ReactorType EQ 'EpiPro'))
                                    IncludeReactor  = False$
                                Case MatchType EQ 'EpiPro' AND Not((ReactorType EQ 'EPP' OR ReactorType EQ 'EpiPro'))
                                    IncludeReactor  = False$
                                Case MatchType _EQC 'GAN' AND Not(ReactorType _EQC 'GAN')
                                    IncludeReactor  = False$
                                Case MatchType NE ReactorType
                                    IncludeReactor  = False$
                            End Case
                        Case MatchSize NE ''
                            If MatchSize NE ReactorSize then IncludeReactor  = False$
                    End Case
                    If IncludeReactor then
                        ReactorNumbers := ReactorNo : @FM
                    end
                end
            Repeat
            ReactorNumbers[-1, 1]   = ''
            Memory_Services('SetValue', ServiceKeyID, ReactorNumbers)
        end
    end
    
    Response    = ReactorNumbers
    
end service


//----------------------------------------------------------------------------------------------------------------------
// GetReactor
//
// Returns a JSON formatted object of information related to the indicated reactor number.
//----------------------------------------------------------------------------------------------------------------------
Service GetReactor(ReactorNo)
    
    ServiceKeyID   := '*' : ReactorNo
    Reactor         = ''
    
    If Memory_Services('IsValueCurrent', ServiceKeyID, 3600, True$) then
        Reactor     = Memory_Services('GetValue', ServiceKeyID)
    end else
        If (ReactorNo NE '') AND (ReactorNo NE 0) then
            ReactorRow  = Database_Services('ReadDataRow', 'REACTOR', ReactorNo)
            If SRP_JSON(objReactor, 'NEW', 'OBJECT') then
                ReactorType     = ReactorRow<REACTOR_REACT_TYPE$>
                SRP_JSON(objReactor, 'SETVALUE', 'ReactorNumber', ReactorNo, 'STRING')
                SRP_JSON(objReactor, 'SETVALUE', 'Type', ReactorType, 'STRING')
                Begin Case
                    Case ReactorType _EQC 'ASM' ; ReactorTypeVerbose = 'ASM'
                    Case ReactorType _EQC 'ASM+'; ReactorTypeVerbose = 'ASM+'
                    Case ReactorType _EQC 'EPP' ; ReactorTypeVerbose = 'EpiPro'
                    Case ReactorType _EQC 'EPS' ; ReactorTypeVerbose = 'ASM'
                    Case ReactorType _EQC 'HTR' ; ReactorTypeVerbose = 'HTR'
                    Case ReactorType _EQC 'GAN' ; ReactorTypeVerbose = 'GaN'
                    Case ReactorType _EQC ''	; ReactorTypeVerbose = '***'
                End Case
                SRP_JSON(objReactor, 'SETVALUE', 'TypeVerbose', ReactorTypeVerbose, 'STRING')
                SRP_JSON(objReactor, 'SETVALUE', 'AssignmentCode', ReactorRow<REACTOR_REACT_ASSIGNMENT$>, 'STRING')
                SRP_JSON(objReactor, 'SETVALUE', 'AssignmentDescription', Oconv(ReactorRow<REACTOR_REACT_ASSIGNMENT$>, '[REACT_ASSIGN_CONV]'), 'STRING')
                SRP_JSON(objReactor, 'SETVALUE', 'Location', ReactorRow<REACTOR_LOCATIONX$>, 'STRING')
                SRP_JSON(objReactor, 'SETVALUE', 'SusceptorSize', ReactorRow<REACTOR_SUSC_POCKET_SIZE$>, 'STRING')
                SRP_JSON(objReactor, 'SETVALUE', 'SecondChamber', ReactorRow<REACTOR_SECOND_CHAMBER$>, 'STRING')
                SRP_JSON(objReactor, 'SETVALUE', 'PickPlace', ReactorRow<REACTOR_PICK_PLACE$>, 'STRING')
                SRP_JSON(objReactor, 'SETVALUE', 'NotRepairable', ReactorRow<REACTOR_NOT_REPAIRABLE$>, 'STRING')
                Reactor     = SRP_JSON(objReactor, 'STRINGIFY', 'FAST')
                Memory_Services('SetValue', ServiceKeyID, Reactor)
                SRP_JSON(objReactor, 'RELEASE')
            end else
                Error_Services('Add', 'Error creating objReactor in the ' : Service : ' service.')
            end
        end else
            Error_Services('Add', 'ReactorNo argument was missing from the ' : Service : ' service.')
        end
    end
    
    Response    = Reactor
    
end service


//----------------------------------------------------------------------------------------------------------------------
// CreatePerformanceTrackers
//
// Creates performance tracking database rows in the REACTOR_PERFORMANCE table. This creates a unique tracker for each
// Reactor/Part pair based on the indicated dates scheduled work orders.
//----------------------------------------------------------------------------------------------------------------------
Service CreatePerformanceTrackers(ScheduleDate, CreateReports)
    
    // Force the auto scheduler to run to make sure that all adjustments are being made to the scheduler.
    *    Schedule_Services('AutoScheduler')
    
    If Num(ScheduleDate) else ScheduleDate = Iconv(ScheduleDate, 'D')
    If ScheduleDate EQ '' then ScheduleDate = Date()
    If CreateReports NE False$ then CreateReports = True$
    
    // There will be two procedures. The first is a pass through the scheduled events to create the Reactor Performance
    // rows. The second is a pass through the recently created Reactor Performance records to adjust the expected
    // wafer count based part changes.
    
    // Get a list of all the scheduled events for the indicated schedule date. Note: this service expects to be ran
    // shortly after midnight for the indicated schedule date.
    ScheduleEvents  = Schedule_Services('GetScheduleEvents', ScheduleDate, ScheduleDate, '', '', False$)
    If ScheduleEvents NE '' then
        PerfKeyIDs  = ''
        For Each ScheduleEvent in ScheduleEvents using @FM
            KeyIDs          = ScheduleEvent<0, 2>
            WorkOrderNo     = KeyIDs[1, '*']
            EpiPartNo       = KeyIDs[Col2() + 1, '*']
            ReactorNo       = KeyIDs[Col2() + 1, '*']
            ScheduleDate    = KeyIDs[Col2() + 1, '*']
            Sequence        = KeyIDs[Col2() + 1, '*']
            If EpiPartNo NE '' then
                PartUsedKeyID   = Service : '*' : ReactorNo : '*' : ScheduleDate : '*' : EpiPartNo
                If Memory_Services('KeyExists', PartUsedKeyID) EQ False$ then
                    PartCountKeyID  = Service : '*' : ReactorNo : '*' : ScheduleDate
                    NumParts        = Memory_Services('GetValue', PartCountKeyID)
                    NumParts       += 1
                    Memory_Services('SetValue', PartCountKeyID, NumParts)
                    Memory_Services('SetValue', PartUsedKeyID, True$)
                end
                // This is a work order event rather than a block out event.
                ScheduleEventSummary    = Schedule_Services('GetScheduleEventSummary', ReactorNo, WorkOrderNo, ScheduleDate, Sequence, True$)
                StartDate               = Iconv(ScheduleEventSummary<6>, 'D')
                ReactorType             = ScheduleEventSummary<1>
                EndDate                 = Iconv(ScheduleEventSummary<7>, 'D')
                TotalWafers             = ScheduleEventSummary<9>
                Begin Case
                    Case ReactorType _eqc 'ASM+'    ; ReactorType = 'ASM+'
                    Case ReactorType _eqc 'EPP'     ; ReactorType = 'EpiPro'
                    Case ReactorType _eqc 'EPS'     ; ReactorType = 'ASM'
                    Case ReactorType _eqc 'HTR'     ; ReactorType = 'HTR'
                    Case ReactorType _eqc 'GAN'     ; ReactorType = 'GaN'
                    Case ReactorType _eqc ''	    ; ReactorType = '***'
                End Case
                MinutesPerWafer = Epi_Part_Services('GetMinutesPerWaferScheduler', EpiPartNo, ReactorType)
                WafersPerDay    = Epi_Part_Services('GetAdjustedWafersPerDayScheduler', EpiPartNo, ReactorType, MinutesPerWafer)
                If ScheduleDate LT EndDate then
                    // The end date of this schedule event is still in the future. Therefore, the current event is expected
                    // to run the whole day. Use the calculated wafers per day.
                    WafersExpected  = WafersPerDay
                end else
                    // This is the end of the schedule event. Wafers expected will be the remaining total wafers after
                    // all previous wafers per day have been deducted.
                    TotalPreviousDays   = EndDate - StartDate
                    TotalPreviousWafers = TotalPreviousDays * WafersPerDay
                    WafersExpected      = TotalWafers - TotalPreviousWafers
                    If WafersExpected GT WafersPerDay then WafersExpected = WafersPerDay    ; // Cap to the wafers per day.
                end
                If WafersExpected LT 0 then WafersExpected = 0
                // Full KeyID = {REACTOR_NO} * {EPI_PART_NO} * {WEEK_YEAR} * {WEEK_NO} * {SCHEDULE_DATE}
                WeekDate    = Date_Services('GetISOWeekDate', ScheduleDate)
                WeekYear    = WeekDate[1, '-']
                WeekNo      = WeekDate[Col2() + 1, '-']
                PerfKeyID   = ReactorNo : '*' : EpiPartNo : '*' : WeekYear : '*' : WeekNo : '*' : ScheduleDate
                PerfRow     = Database_Services('ReadDataRow', 'REACTOR_PERFORMANCE', PerfKeyID, True$, 0)
                PerfRow<2>  = PerfRow<2> + Iconv(WafersExpected, 'MD2')
                Database_Services('WriteDataRow', 'REACTOR_PERFORMANCE', PerfKeyID, PerfRow, True$, False$, True$)
                Locate PerfKeyID in PerfKeyIDs using @FM setting fPos else PerfKeyIDs := PerfKeyID : @FM
            end
        Next ScheduleEvent
        
        // Pass through each Reactor Performance row. For every part being run on a given reactor, the following
        // adjustment is required:
        //
        //   1. Total expected amount per part should be prorated by the number of parts. This accounts for the fact
        //      that one reactor will be splitting its time for each part. For example, if Part A is normally estimated
        //      to run 200 parts and Part B is normally estimated to run 300 parts, they will each be prorated by .5.
        //      Thus, Part A will now be estimated to run 100 parts and Part B will be estimated to run 150 parts.
        //
        //   2. Since each part change requires an average of 6 hours of down time to prepare the reactor, the amount
        //      per part should be prorated by the remaining hours in the day. For example, if there are two parts being
        //      ran on the reactor, this only allows 18 hours (out of 24) to run parts, which is a prorated value of
        //      .75. Using the above example, Part A would then have 100 parts prorated to 75 (100 * .75) and Part would
        //      then have 150 parts prorated to 112.50 (150 * .75).
        If PerfKeyIDs NE '' then
            For Each PerfKeyID in PerfKeyIDs using @FM
                ReactorNo       = PerfKeyID[1, '*']
                EpiPartNo       = PerfKeyID[Col2() + 1, '*']
                WeekYear        = PerfKeyID[Col2() + 1, '*']
                WeekNo          = PerfKeyID[Col2() + 1, '*']
                ScheduleDate    = PerfKeyID[Col2() + 1, '*']
                PartCountKeyID  = Service : '*' : ReactorNo : '*' : ScheduleDate
                NumParts        = Memory_Services('GetValue', PartCountKeyID)
                If NumParts GT 1 then
                    // Only prorate if there is more then one part.
                    Prorate1    = 1 / NumParts                          ; // Adjust for the number of parts.
                    Prorate2    = ((24 - ((NumParts - 1) * 6) ) / 24)   ; // Adjust for the number of hours remaining in the day.
                    Prorate     = Prorate1 * Prorate2
                    PerfRow     = Database_Services('ReadDataRow', 'REACTOR_PERFORMANCE', PerfKeyID, True$, 0)
                    PerfRow<2>  = PerfRow<2> * ProRate
                    Database_Services('WriteDataRow', 'REACTOR_PERFORMANCE', PerfKeyID, PerfRow, True$, False$, True$)
                end
            Next PerfKeyID
            
            // Clear out all cached data.
            Memory_Services('ReleaseHashTable')
        end
    end
    
    If CreateReports then
        Reactor_Services('CreateDailyPerformanceReport', ScheduleDate)
        If Mod(ScheduleDate, 7) EQ 5 then
            // This is early Friday. Create the weekly performance report for the upcoming week. First, the
            // CreatePerformanceTrackers must be called for all days in the upcoming week.
            NextMonday  = ScheduleDate + 3
            NextSunday  = ScheduleDate + 9
            For ScheduleDate = NextMonday to NextSunday
                Reactor_Services('CreatePerformanceTrackers', ScheduleDate, False$)
            Next ScheduleDate
            // Create the weekly performance report based on the daily performance tracking data.
            YearWeekNo = Date_Services('GetISOWeekDate', NextMonday)
            YearWeekNo = Field(YearWeekNo, '-', 1, 2)
            Reactor_Services('CreateWeeklyPerformanceReport', YearWeekNo)
            // Clear the daily performance tracking data for the upcoming week since these will be recreated
            // automatically on the morning of each date.
            For ScheduleDate = NextMonday to NextSunday
                Reactor_Services('ClearWafersExpected', ScheduleDate)
            Next ScheduleDate
        end
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// UpdatePerformanceTrackers
//
// Updates the wafers processed column in the performance tracking database rows.
//----------------------------------------------------------------------------------------------------------------------
Service UpdatePerformanceTrackers(ScheduleDate, CreateReports)   
    
    If Num(ScheduleDate) else ScheduleDate = Iconv(ScheduleDate, 'D')
    If ScheduleDate EQ '' then ScheduleDate = Date()
    If CreateReports NE False$ then CreateReports = True$
    
    rv          = Set_Status(0)
    Sentence    = 'SELECT RDS WITH DATE_OUT EQ ' : Quote(Oconv(ScheduleDate, 'D4/'))
    RList(Sentence, 5, '', '', '')
    EOF         = False$
    Loop
        Readnext RDSID else EOF = True$
    Until EOF
        RDSRow  = Database_Services('ReadDataRow', 'RDS', RDSID)
        If Error_Services('NoError') then
            WeekDate    = Date_Services('GetISOWeekDate', ScheduleDate)
            WeekYear    = WeekDate[1, '-']
            WeekNo      = WeekDate[Col2() + 1, '-']
            ReactorNo   = RDSRow<RDS_REACTOR$>
            ReactorRow  = Database_Services('ReadDataRow', 'REACTOR', ReactorNo, True$, 60, False$)
            ReactorType = ReactorRow<REACTOR_REACT_TYPE$>
            If ReactorType EQ 'EPP' then
                // This is an EpiPro reactor. Check to see if has a second chamber. If so, then this is the primary
                // reactor. If not, then check the previous reactor to see if it references this current reactor as its
                // second chamber. If so then use the previous reactor to track the wafers processed.
                SecondChamber   = ReactorRow<REACTOR_SECOND_CHAMBER$>
                If SecondChamber EQ '' then
                    CompareReactorNo        = ReactorNo - 2
                    CompareReactorRow       = Database_Services('ReadDataRow', 'REACTOR', CompareReactorNo, True$, 60, False$)
                    CompareSecondChamber    = CompareReactorRow<REACTOR_SECOND_CHAMBER$>
                    If CompareSecondChamber EQ ReactorNo then Transfer CompareReactorNo to ReactorNo
                end
            end
            
            *            EpiPartNo   = RDSRow<RDS_PART_NUM$>
            EpiPartNo   = Xlate('RDS', RDSID, 'EPI_PART_NO', 'X')
            // Full KeyID = {REACTOR_NO} * {EPI_PART_NO} * {WEEK_YEAR} * {WEEK_NO} * {SCHEDULE_DATE}
            PerfKeyID   = ReactorNo : '*' : EpiPartNo : '*' : WeekYear : '*' : WeekNo : '*' : ScheduleDate
            PerfRow     = Database_Services('ReadDataRow', 'REACTOR_PERFORMANCE', PerfKeyID, True$, 0)
            If Error_Services('NoError') then
                WafersProcessed = RDSRow<RDS_WAFERS_IN$>
                PerfRow<3>      = PerfRow<3> + Iconv(WafersProcessed, 'MD2')
                Database_Services('WriteDataRow', 'REACTOR_PERFORMANCE', PerfKeyID, PerfRow, True$, False$, True$)
            end
        end
    Repeat
    
    If CreateReports then
        Reactor_Services('UpdateDailyPerformanceReport', ScheduleDate)
        If Mod(ScheduleDate, 7) EQ 0 then
            // This is a Sunday, which means the last day of a work week has been updated. Update the weekly performance
            // report but set the date to the previous Monday.
            YearWeekNo = Date_Services('GetISOWeekDate', ScheduleDate - 6)
            YearWeekNo = Field(YearWeekNo, '-', 1, 2)
            Reactor_Services('UpdateWeeklyPerformanceReport', ScheduleDate - 6)
        end
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// ClearWafersExpected
//
// Clears the expected wafers for all database rows the REACTOR_PERFORMANCE table based on the indicated schedule date.
//----------------------------------------------------------------------------------------------------------------------
Service ClearWafersExpected(ScheduleDate)
    
    If Num(ScheduleDate) else ScheduleDate = Iconv(ScheduleDate, 'D')
    If ScheduleDate EQ '' then ScheduleDate = Date()
    
    hReactors   = Database_Services('GetTableHandle', 'REACTOR')
    If Error_Services('NoError') then
        If Error_Services('NoError') then
            rv          = Set_Status(0)
            RList('SELECT REACTOR BY REACT_NO', 5, '', '', '')
            Done        = False$
            ReportData  = ''
            Loop
                Readnext ReactorNo else Done = True$
            Until Done
                ReactorRow      = Database_Services('ReadDataRow', 'REACTOR', ReactorNo, True$, 0)
                ReactorType     = ReactorRow<REACTOR_REACT_TYPE$>
                SecondChamber   = ReactorRow<REACTOR_SECOND_CHAMBER$>
                @DICT           = Database_Services('GetTableHandle', 'DICT.REACTOR_PERFORMANCE')
                If (((ReactorNo GT 0) AND (ReactorNo LE 79))) AND ((ReactorType EQ 'EPP' AND SecondChamber NE '') OR (ReactorType NE 'EPP')) then
                    *                If (ReactorNo NE 0) AND (ReactorNo LE 74) AND ((ReactorType EQ 'EPP' AND SecondChamber NE '') OR (ReactorType NE 'EPP')) AND (ReactorType NE 'GAN') then
                    @ID     = ReactorNo : '****' : ScheduleDate
                    @RECORD = Database_Services('ReadDataRow', 'REACTOR_PERFORMANCE', @ID, True$, 0)
                    If Error_Services('NoError') then
                        DetailPerfKeyIDs    = {ALL_DETAIL_CHILD_KEY_IDS}
                        If DetailPerfKeyIDs NE '' then
                            For Each PerfKeyID in DetailPerfKeyIDs using @VM
                                PerfRow     = Database_Services('ReadDataRow', 'REACTOR_PERFORMANCE', PerfKeyID, True$, 0)
                                PerfRow<2>  = ''
                                Database_Services('WriteDataRow', 'REACTOR_PERFORMANCE', PerfKeyID, PerfRow, True$, False$, True$)
                            Next DetailPerfKeyID
                        end
                    end
                end
            Repeat
        end
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// ClearWafersProcessed
//
// Clears the processed wafers for all database rows the REACTOR_PERFORMANCE table based on the indicated schedule date.
//----------------------------------------------------------------------------------------------------------------------
Service ClearWafersProcessed(ScheduleDate)
    
    If Num(ScheduleDate) else ScheduleDate = Iconv(ScheduleDate, 'D')
    If ScheduleDate EQ '' then ScheduleDate = Date()
    
    hReactors   = Database_Services('GetTableHandle', 'REACTOR')
    If Error_Services('NoError') then
        If Error_Services('NoError') then
            rv          = Set_Status(0)
            RList('SELECT REACTOR BY REACT_NO', 5, '', '', '')
            Done        = False$
            ReportData  = ''
            Loop
                Readnext ReactorNo else Done = True$
            Until Done
                ReactorRow      = Database_Services('ReadDataRow', 'REACTOR', ReactorNo, True$, 0)
                ReactorType     = ReactorRow<REACTOR_REACT_TYPE$>
                SecondChamber   = ReactorRow<REACTOR_SECOND_CHAMBER$>
                @DICT           = Database_Services('GetTableHandle', 'DICT.REACTOR_PERFORMANCE')
                If (((ReactorNo GT 0) AND (ReactorNo LE 79))) AND ((ReactorType EQ 'EPP' AND SecondChamber NE '') OR (ReactorType NE 'EPP')) then
                    *                If (ReactorNo NE 0) AND (ReactorNo LE 74) AND ((ReactorType EQ 'EPP' AND SecondChamber NE '') OR (ReactorType NE 'EPP')) AND (ReactorType NE 'GAN') then
                    @ID     = ReactorNo : '****' : ScheduleDate
                    @RECORD = Database_Services('ReadDataRow', 'REACTOR_PERFORMANCE', @ID, True$, 0)
                    If Error_Services('NoError') then
                        DetailPerfKeyIDs    = {ALL_DETAIL_CHILD_KEY_IDS}
                        If DetailPerfKeyIDs NE '' then
                            For Each PerfKeyID in DetailPerfKeyIDs using @VM
                                PerfRow     = Database_Services('ReadDataRow', 'REACTOR_PERFORMANCE', PerfKeyID, True$, 0)
                                PerfRow<3>  = ''
                                Database_Services('WriteDataRow', 'REACTOR_PERFORMANCE', PerfKeyID, PerfRow, True$, False$, True$)
                            Next DetailPerfKeyID
                        end
                    end
                end
            Repeat
        end
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// CreateDailyPerformanceReport
//
// Creates daily performance tracking report using the database rows in the REACTOR_PERFORMANCE table. This creates an
// Excel spreadsheet using the "Fab Performance Daily Template.xlsx" file as a template.
//----------------------------------------------------------------------------------------------------------------------
Service CreateDailyPerformanceReport(ScheduleDate)
    
    If Num(ScheduleDate) else ScheduleDate = Iconv(ScheduleDate, 'D')
    If ScheduleDate EQ '' then ScheduleDate = Date()
    
    DisplayDate     = Oconv(ScheduleDate, 'D4/')
    XLSXTemplate    = 'Fab Performance Daily Template.xlsx'
    objDocument     = Excel_Services('OpenDocument', TemplatesFolder : XLSXTemplate)
    If Error_Services('NoError') then
        hReactors   = Database_Services('GetTableHandle', 'REACTOR')
        If Error_Services('NoError') then
            If Error_Services('NoError') then
                rv          = Set_Status(0)
                RList('SELECT REACTOR BY REACT_NO', 5, '', '', '')
                Done        = False$
                ReportData  = ''
                Loop
                    Readnext ReactorNo else Done = True$
                Until Done
                    ReactorRow      = Database_Services('ReadDataRow', 'REACTOR', ReactorNo, True$, 0)
                    ReactorType     = ReactorRow<REACTOR_REACT_TYPE$>
                    SecondChamber   = ReactorRow<REACTOR_SECOND_CHAMBER$>
                    @DICT           = Database_Services('GetTableHandle', 'DICT.REACTOR_PERFORMANCE')
                    If (((ReactorNo GT 0) AND (ReactorNo LE 79))) AND ((ReactorType EQ 'EPP' AND SecondChamber NE '') OR (ReactorType NE 'EPP')) then
                        *                    If (ReactorNo NE 0) AND (ReactorNo LE 74) AND ((ReactorType EQ 'EPP' AND SecondChamber NE '') OR (ReactorType NE 'EPP')) AND (ReactorType NE 'GAN') then
                        PerfKeyID           = ReactorNo : '****' : ScheduleDate
                        PerfRow             = Database_Services('ReadDataRow', 'REACTOR_PERFORMANCE', PerfKeyID, True$, 0)
                        DetailPerfKeyIDs    = PerfRow<1>
                        If DetailPerfKeyIDs NE '' then
                            For Each @ID in DetailPerfKeyIDs using @VM
                                @RECORD                 = Database_Services('ReadDataRow', 'REACTOR_PERFORMANCE', @ID, True$, 0)
                                ReactorName             = {REACTOR_NAME}
                                EpiPartNo               = {EPI_PART_NO}
                                WafersExpected          = Oconv({TOTAL_WAFERS_EXPECTED}, 'MD2')
                                WafersExpectedRounded   = Oconv({TOTAL_WAFERS_EXPECTED_ROUNDED}, 'MD2')
                                ReportData             := ReactorType : @VM : ReactorName : @VM : DisplayDate : @VM : EpiPartNo : @VM : WafersExpected : @VM : WafersExpectedRounded : @VM : ReactorNo : @FM
                            Next DetailPerfKeyID
                        end else
                            ReportData := ReactorType : @VM : ReactorNo : @VM : DisplayDate : @VM : '' : @VM : '' : @VM : '' : @VM : ReactorNo : @FM
                        end
                    end
                Repeat
                ReportData[-1, 1]   = ''    ; // Strip off the last @FM
                ReportData          = SRP_Array('SortRows', ReportData, 'AR7' : @VM : 'AL4', 'LIST', @FM, @VM)
                CellRow             = 1     ; // Initialize the cell row to start entering data into.
                
                If ReportData NE '' then
                    For Each ReportRow in ReportData using @FM
                        ReactorType             = ReportRow[1, @VM]
                        ReactorName             = ReportRow[Col2() + 1, @VM]
                        DisplayDate             = ReportRow[Col2() + 1, @VM]
                        EpiPartNo               = ReportRow[Col2() + 1, @VM]
                        WafersExpected          = ReportRow[Col2() + 1, @VM]
                        WafersExpectedRounded   = ReportRow[Col2() + 1, @VM]
                        CellRow                += 1
                        Excel_Services('SetCellValue', objDocument, 'SSR', 'A', CellRow, ReactorType)
                        Excel_Services('SetCellValue', objDocument, 'SSR', 'B', CellRow, ReactorName)
                        Excel_Services('SetCellValue', objDocument, 'SSR', 'E', CellRow, DisplayDate)
                        Excel_Services('SetCellValue', objDocument, 'SSR', 'F', CellRow, EpiPartNo)
                        Excel_Services('SetCellValue', objDocument, 'SSR', 'G', CellRow, WafersExpected)
                        Excel_Services('SetCellValue', objDocument, 'SSR', 'H', CellRow, WafersExpectedRounded)
                    Next ReportRow
                end
            end
            NewDocument = DisplayDate[7, 4] : '-' : DisplayDate[1, 2] : '-' : DisplayDate[4, 2] : ' Fab Performance.xlsx'
            Excel_Services('SaveDocument', objDocument, DailyReportsFolder : NewDocument)
            If Error_Services('NoError') then
                Call Send_SRP_Mail(DailyReportsFolder : NewDocument)
                rv  = Set_Status(0)
                OSRead PerformanceReport from DailyReportsFolder : NewDocument then
                    OSWrite PerformanceReport to DailyReportsFolder : NewDocument
                    If Status() then
                        Error_Services('Add', 'Error writing ' : DailyReportsFolder : NewDocument : ' in the ' : Service : ' service. Status: ' : Status())
                    end
                end else
                    Error_Services('Add', 'Error reading ' : DailyReportsFolder : NewDocument : ' in the ' : Service : ' service. Status: ' : Status())
                end
            end
        end
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// UpdateDailyPerformanceReport
//
// Creates daily performance tracking report using the database rows in the REACTOR_PERFORMANCE table. This creates an
// Excel spreadsheet using the "Fab Performance Daily Template.xlsx" file as a template.
//----------------------------------------------------------------------------------------------------------------------
Service UpdateDailyPerformanceReport(ScheduleDate)
    
    If Num(ScheduleDate) else ScheduleDate = Iconv(ScheduleDate, 'D')
    If ScheduleDate EQ '' then ScheduleDate = Date()
    
    DisplayDate     = Oconv(ScheduleDate, 'D4/')
    Document        = DisplayDate[7, 4] : '-' : DisplayDate[1, 2] : '-' : DisplayDate[4, 2] : ' Fab Performance.xlsx'
    If Excel_Services('DocumentExists', DailyReportsFolder : Document) then
        objDocument     = Excel_Services('OpenDocument', DailyReportsFolder : Document)
    end else
        Error_Services('Add', 'Unable to find ' : Document : ' in the ' : Service : ' service.')
    end
    If Error_Services('NoError') then
        @DICT           = Database_Services('GetTableHandle', 'DICT.REACTOR_PERFORMANCE')
        If Error_Services('NoError') then
            CellRow             = 1     ; // Initialize the cell row to start entering data into.
            Loop
                CellRow    += 1
                Date        = Excel_Services('GetCellValue', objDocument, 'SSR', 'E', CellRow)
            Until Date EQ ''
                ReactorName        = Excel_Services('GetCellValue', objDocument, 'SSR', 'B', CellRow)
                ReactorNo          = ReactorName[1, 'F/']
                EpiPartNo          = Excel_Services('GetCellValue', objDocument, 'SSR', 'F', CellRow)
                WeekDate           = Date_Services('GetISOWeekDate', ScheduleDate)
                WeekYear           = WeekDate[1, '-']
                WeekNo             = WeekDate[Col2() + 1, '-']
                // Full KeyID = {REACTOR_NO} * {EPI_PART_NO} * {WEEK_YEAR} * {WEEK_NO} * {SCHEDULE_DATE}
                @ID                = ReactorNo : '*' : EpiPartNo : '*' : WeekYear : '*' : WeekNo : '*' : ScheduleDate
                @RECORD            = Database_Services('ReadDataRow', 'REACTOR_PERFORMANCE', @ID, True$, 0)
                WafersProcessed    = Oconv({TOTAL_WAFERS_PROCESSED}, 'MD2')
                WafersDelta        = Oconv({TOTAL_WAFERS_DELTA}, 'MD2')
                WafersDeltaRounded = Oconv({TOTAL_WAFERS_DELTA_ROUNDED}, 'MD2')
                Excel_Services('SetCellValue', objDocument, 'SSR', 'I', CellRow, WafersProcessed)
                Excel_Services('SetCellValue', objDocument, 'SSR', 'K', CellRow, WafersDelta)
                Excel_Services('SetCellValue', objDocument, 'SSR', 'L', CellRow, WafersDeltaRounded)
                //Comment            = Excel_Services('GetCellValue', objDocument, 'SSR', 'M', CellRow)
                //Excel_Services('SetCellValue', objDocument, 'SSR', 'M', CellRow, Comment)
            Repeat
            Excel_Services('SaveDocument', objDocument)
            If Error_Services('NoError') then
                Call Send_SRP_Mail(DailyReportsFolder : Document)
                rv  = Set_Status(0)
                OSRead PerformanceReport from DailyReportsFolder : Document then
                    OSWrite PerformanceReport to DailyReportsFolder : Document
                    If Status() then
                        Error_Services('Add', 'Error writing ' : DailyReportsFolder : Document : ' in the ' : Service : ' service. Status: ' : Status())
                    end
                end else
                    Error_Services('Add', 'Error reading ' : DailyReportsFolder : Document : ' in the ' : Service : ' service. Status: ' : Status())
                end
            end else
                ErrorMessage = Error_Services('GetMessage')
            end
        end
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// CreateWeeklyPerformanceReport
//
// Creates weekly performance tracking report using the database rows in the REACTOR_PERFORMANCE table. This creates an
// Excel spreadsheet using the "Fab Performance Weekly Template.xlsx" file as a template.
//----------------------------------------------------------------------------------------------------------------------
Service CreateWeeklyPerformanceReport(YearWeekNo)
    
    // YearWeekNo looks like 2017-W44. For testing purposes it might be easier to pass in a date and the YearWeekNo
    // will be calculated.
    If (Num(Iconv(YearWeekNo, 'D')) AND (Iconv(YearWeekNo, 'D') NE '')) OR Num(YearWeekNo) then
        // A internal or external formatted date was passed in.
        YearWeekNo = Date_Services('GetISOWeekDate', YearWeekNo)
        YearWeekNo = Field(YearWeekNo, '-', 1, 2)
    end
    
    WeekYear        = YearWeekNo[1, '-']
    WeekNo          = YearWeekNo[Col2() + 1, '-']
    XLSXTemplate    = 'Fab Performance Weekly Template.xlsx'
    objDocument     = Excel_Services('OpenDocument', TemplatesFolder : XLSXTemplate)
    If Error_Services('NoError') then
        hReactors   = Database_Services('GetTableHandle', 'REACTOR')
        If Error_Services('NoError') then
            If Error_Services('NoError') then
                rv          = Set_Status(0)
                RList('SELECT REACTOR BY REACT_NO', 5, '', '', '')
                Done        = False$
                ReportData  = ''
                Loop
                    Readnext ReactorNo else Done = True$
                Until Done
                    ReactorRow      = Database_Services('ReadDataRow', 'REACTOR', ReactorNo, True$, 0)
                    ReactorType     = ReactorRow<REACTOR_REACT_TYPE$>
                    SecondChamber   = ReactorRow<REACTOR_SECOND_CHAMBER$>
                    @DICT           = Database_Services('GetTableHandle', 'DICT.REACTOR_PERFORMANCE')
                    If (((ReactorNo GT 0) AND (ReactorNo LE 79))) AND ((ReactorType EQ 'EPP' AND SecondChamber NE '') OR (ReactorType NE 'EPP')) then
                        *                    If (ReactorNo NE 0) AND (ReactorNo LE 74) AND ((ReactorType EQ 'EPP' AND SecondChamber NE '') OR (ReactorType NE 'EPP')) AND (ReactorType NE 'GAN') then
                        PerfKeyID           = ReactorNo : '**' : WeekYear : '*' : WeekNo : '*'
                        PerfRow             = Database_Services('ReadDataRow', 'REACTOR_PERFORMANCE', PerfKeyID, True$, 0)
                        DetailPerfKeyIDs    = PerfRow<1>
                        If DetailPerfKeyIDs NE '' then
                            DetailPerfKeyIDs            = SRP_Array('SortRows', DetailPerfKeyIDs, 'AL2', 'LIST', @VM, '*')
                            PrevEpiPartNo               = ''
                            TotalWafersExpected         = 0
                            TotalWafersExpectedRounded  = 0
                            For Each @ID in DetailPerfKeyIDs using @VM
                                @RECORD                     = Database_Services('ReadDataRow', 'REACTOR_PERFORMANCE', @ID, True$, 0)
                                ReactorName                 = {REACTOR_NAME}
                                EpiPartNo                   = {EPI_PART_NO}
                                If (EpiPartNo NE PrevEpiPartNo) AND (PrevepiPartNo NE '') then
                                    // Epi Part No has changed. Add the current totals for the previous Epi Part No to
                                    // the report and reset the variables.
                                    ReportData                 := ReactorType : @VM : ReactorName : @VM : YearWeekNo : @VM : PrevEpiPartNo : @VM : TotalWafersExpected : @VM : TotalWafersExpectedRounded : @VM : ReactorNo : @FM
                                    TotalWafersExpected         = 0
                                    TotalWafersExpectedRounded  = 0
                                end
                                PrevEpiPartNo               = EpiPartNo
                                TotalWafersExpected        += Oconv({TOTAL_WAFERS_EXPECTED}, 'MD2')
                                TotalWafersExpectedRounded += Oconv({TOTAL_WAFERS_EXPECTED_ROUNDED}, 'MD2')
                            Next DetailPerfKeyID
                            // Add the final total values to the report.
                            ReportData := ReactorType : @VM : ReactorName : @VM : YearWeekNo : @VM : PrevEpiPartNo : @VM : TotalWafersExpected : @VM : TotalWafersExpectedRounded : @VM : ReactorNo : @FM
                        end else
                            ReportData := ReactorType : @VM : ReactorNo : @VM : YearWeekNo : @VM : '' : @VM : '' : @VM : '' : @VM : ReactorNo : @FM
                        end
                    end
                Repeat
                ReportData[-1, 1]   = ''    ; // Strip off the last @FM
                ReportData          = SRP_Array('SortRows', ReportData, 'AR7' : @VM : 'AL4', 'LIST', @FM, @VM)
                CellRow             = 1     ; // Initialize the cell row to start entering data into.
                
                If ReportData NE '' then
                    For Each ReportRow in ReportData using @FM
                        ReactorType             = ReportRow[1, @VM]
                        ReactorName             = ReportRow[Col2() + 1, @VM]
                        YearWeekNo              = ReportRow[Col2() + 1, @VM]
                        EpiPartNo               = ReportRow[Col2() + 1, @VM]
                        WafersExpected          = ReportRow[Col2() + 1, @VM]
                        WafersExpectedRounded   = ReportRow[Col2() + 1, @VM]
                        CellRow                += 1
                        Excel_Services('SetCellValue', objDocument, 'SSR', 'A', CellRow, ReactorType)
                        Excel_Services('SetCellValue', objDocument, 'SSR', 'B', CellRow, ReactorName)
                        Excel_Services('SetCellValue', objDocument, 'SSR', 'E', CellRow, YearWeekNo)
                        Excel_Services('SetCellValue', objDocument, 'SSR', 'F', CellRow, EpiPartNo)
                        Excel_Services('SetCellValue', objDocument, 'SSR', 'G', CellRow, WafersExpected)
                        Excel_Services('SetCellValue', objDocument, 'SSR', 'H', CellRow, WafersExpectedRounded)
                    Next ReportRow
                end
            end
            NewDocument = YearWeekNo : ' Fab Performance.xlsx'
            Excel_Services('SaveDocument', objDocument, WeeklyReportsFolder : NewDocument)
            If Error_Services('NoError') then
                Call Send_SRP_Mail(WeeklyReportsFolder : NewDocument)
                rv  = Set_Status(0)
                OSRead PerformanceReport from WeeklyReportsFolder : NewDocument then
                    OSWrite PerformanceReport to WeeklyReportsFolder : NewDocument
                    If Status() then
                        Error_Services('Add', 'Error writing ' : WeeklyReportsFolder : NewDocument : ' in the ' : Service : ' service. Status: ' : Status())
                    end
                end else
                    Error_Services('Add', 'Error reading ' : WeeklyReportsFolder : NewDocument : ' in the ' : Service : ' service. Status: ' : Status())
                end
            end
        end
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// UpdateWeeklyPerformanceReport
//
// Creates daily performance tracking report using the database rows in the REACTOR_PERFORMANCE table. This creates an
// Excel spreadsheet using the "Fab Performance Daily Template.xlsx" file as a template.
//----------------------------------------------------------------------------------------------------------------------
Service UpdateWeeklyPerformanceReport(YearWeekNo)
    
    // YearWeekNo looks like 2017-W44. For testing purposes it might be easier to pass in a date and the YearWeekNo
    // will be calculated.
    If (Num(Iconv(YearWeekNo, 'D')) AND (Iconv(YearWeekNo, 'D') NE '')) OR Num(YearWeekNo) then
        // A internal or external formatted date was passed in.
        YearWeekNo = Date_Services('GetISOWeekDate', YearWeekNo)
        YearWeekNo = Field(YearWeekNo, '-', 1, 2)
    end
    
    WeekYear        = YearWeekNo[1, '-']
    WeekNo          = YearWeekNo[Col2() + 1, '-']
    Document        = YearWeekNo : ' Fab Performance.xlsx'
    If Excel_Services('DocumentExists', WeeklyReportsFolder : Document) then
        objDocument     = Excel_Services('OpenDocument', WeeklyReportsFolder : Document)
    end else
        Error_Services('Add', 'Unable to find ' : Document : ' in the ' : Service : ' service.')
    end
    If Error_Services('NoError') then
        @DICT           = Database_Services('GetTableHandle', 'DICT.REACTOR_PERFORMANCE')
        If Error_Services('NoError') then
            CellRow             = 1     ; // Initialize the cell row to start entering data into.
            Loop
                CellRow    += 1
                Date        = Excel_Services('GetCellValue', objDocument, 'SSR', 'E', CellRow)
            Until Date EQ ''
                ReactorName         = Excel_Services('GetCellValue', objDocument, 'SSR', 'B', CellRow)
                ReactorNo           = ReactorName[1, 'F/']
                EpiPartNo           = Excel_Services('GetCellValue', objDocument, 'SSR', 'F', CellRow)
                // Full KeyID = {REACTOR_NO} * {EPI_PART_NO} * {WEEK_YEAR} * {WEEK_NO} * {SCHEDULE_DATE}
                @ID                 = ReactorNo : '*' : EpiPartNo : '*' : WeekYear : '*' : WeekNo : '*'
                @RECORD             = Database_Services('ReadDataRow', 'REACTOR_PERFORMANCE', @ID, True$, 0)
                WafersProcessed     = Oconv({TOTAL_WAFERS_PROCESSED}, 'MD2')
                WafersDelta         = Oconv({TOTAL_WAFERS_DELTA}, 'MD2')
                WafersDeltaRounded  = Oconv({TOTAL_WAFERS_DELTA_ROUNDED}, 'MD2')
                Excel_Services('SetCellValue', objDocument, 'SSR', 'J', CellRow, WafersProcessed)
                Excel_Services('SetCellValue', objDocument, 'SSR', 'K', CellRow, WafersDelta)
                Excel_Services('SetCellValue', objDocument, 'SSR', 'L', CellRow, WafersDeltaRounded)
            Repeat
            Excel_Services('SaveDocument', objDocument)
            If Error_Services('NoError') then
                Call Send_SRP_Mail(WeeklyReportsFolder : Document)
                rv  = Set_Status(0)
                OSRead PerformanceReport from WeeklyReportsFolder : Document then
                    OSWrite PerformanceReport to WeeklyReportsFolder : Document
                    If Status() then
                        Error_Services('Add', 'Error writing ' : WeeklyReportsFolder : Document : ' in the ' : Service : ' service. Status: ' : Status())
                    end
                end else
                    Error_Services('Add', 'Error reading ' : WeeklyReportsFolder : Document : ' in the ' : Service : ' service. Status: ' : Status())
                end
            end
        end
    end
    
end service



Service GetReactorCurrLoad(ReactNo)
	
    Response = ''
    ReactStatusRec = Database_Services('ReadDataRow', 'REACT_STATUS', ReactNo)
    LoadedCassettes = ReactStatusRec<REACT_STATUS_LOAD_CASS_ID$>
    Response = LoadedCassettes
    
end service


Service GetReactorCurrLoadRDS(ReactNo)
	
    Response = ''
    ReactStatusRec = Database_Services('ReadDataRow', 'REACT_STATUS', ReactNo)
    LoadedCassettes = ReactStatusRec<REACT_STATUS_LOAD_RDS$>
    Response = LoadedCassettes
    
	
	Response = ''
	ReactStatusRec = Database_Services('ReadDataRow', 'REACT_STATUS', ReactNo)
	LoadedCassettes = ReactStatusRec<REACT_STATUS_LOAD_RDS$>
	Response = LoadedCassettes
	
end service



Service GetReactCurrModeRec(ReactNo)
	
	Response        = ''
	ReactModeRecKey = Xlate('REACTOR_CHILD_KEY_IDS_NG', ReactNo, REACTOR_CHILD_KEY_IDS_REACT_MODE_KEY_IDS$, 'X')
	ReactModeRec    = Database_Services('ReadDataRow', 'REACT_MODE_NG', ReactModeRecKey)
	Response        = ReactModeRec
	
end service



Service GetReactCurrModeId(ReactNo)
	
	Response        = ''
	ReactModeRecKey = Xlate('REACTOR_CHILD_KEY_IDS_NG', ReactNo, REACTOR_CHILD_KEY_IDS_REACT_MODE_KEY_IDS$, 'X')
	Response        = ReactModeRecKey
	
end service



Service GetReactCurrModeName(ReactNo)
	
	Response     = ''
	ReactModeRec = Reactor_Services('GetReactCurrModeRec', ReactNo)
	Response     = ReactModeRec<REACT_MODE_NG_MODE$>
	
end service



Service GetReactModeHistory(ReactNo, DaysToReport)

    StartDt = Date() - DaysToReport
    Response   = ''
    LegacyDate = 19749
    If ( (ReactNo NE '') and (DaysToReport NE '') ) then
        If Date() - DaysToReport LE LegacyDate then
            modeArray  = ''
            // New Modes
            SelectSent = 'SELECT REACT_MODE_NG WITH REACT_NO = ':QUOTE(ReactNo):' AND WITH START_DT > ':QUOTE(OConv(StartDt, 'D4/')):'  BY-DSND START_DTM '
            Set_Status(0)
            RList(SelectSent, TARGET_ACTIVELIST$, "", "", "")
            IF Get_Status(errCode) THEN
                ErrMsg(errCode)
                RETURN
            END
            IF @RecCount then
                EoF = 0
                NumKeys = @RecCount
                Cnt = 0
                
                Loop
                    ReadNext Mode Else EoF = 1
                until EoF
                    RowToAdd           = ''
                    ServiceCategories  = XLATE('REACT_MODE_NG', Mode, 'PROB_CAT_DESC', 'X')
                    swap @VM with @svm in ServiceCategories
                    ServiceDescs       = XLATE('REACT_MODE_NG', Mode, 'SERV_DESC', 'X')
                    ReactorLogID       = XLATE('REACT_MODE_NG', Mode, 'START_RL_ID', 'X')
                    StartDTM           = OCONV(XLATE('REACT_MODE_NG', Mode, 'START_DTM', 'X'),'DT2/^HS')
                    ServiceNotes       = XLATE('REACTOR_LOG',ReactorLogID, REACTOR_LOG_NOTES$, 'X')
                    swap @VM with @svm in ServiceDescs
                    RowToAdd <1,1>     = StartDTM                 
                    RowToAdd <1,2>     = OCONV(XLATE('REACT_MODE_NG', Mode,'ELAP_HRS','X'),'MD2,')
                    RowToAdd <1,3>     = OCONV(XLATE('REACT_MODE_NG', Mode, 'MODE', 'X' ),'[REACT_MODE_CONV]')
                    RowToAdd <1,4>     = XLATE('REACT_MODE_NG', Mode, 'START_NOTES', 'X')
                    RowToAdd <1,5>     = ServiceCategories
                    RowToAdd <1,6>     = XLATE('REACT_MODE_NG', Mode, 'START_USER', 'X')
                    RowToAdd <1,7>     = ReactorLogID
                    RowToAdd <1,8>     = ServiceDescs
                    
                    ServiceNotesDtms      = Xlate('REACTOR_LOG', ReactorLogID, REACTOR_LOG_NOTES_DTMS$, 'X')
                    ServiceNotesUsers     = Xlate('REACTOR_LOG', ReactorLogID, REACTOR_LOG_NOTES_USERS$, 'X')
                    
                    ServiceNotesFormatted = ''
                    For each ServiceNote in ServiceNotes using @VM setting vPos
                        ServiceNotesFormatted<0, vPos> = OConv(ServiceNotesDtms<0, vPos>, 'DT2/^H') : ' - ' : OConv(ServiceNotesUsers<0, vPos>, '[CONV_XLATE,LSL_USERS*FIRST_LAST]') : ' - ' : ServiceNote
                    Next ServiceNote
                    
                    NumNotes = DCount(ServiceNotesFormatted, @VM)
                    // Wrap text using CRLF$
                    For NoteIndex = 1 to NumNotes
                        Note    = ServiceNotesFormatted<0, NoteIndex>
                        NoteLen = Len(Note)
                        If NoteLen GT 100 then
                            NumLoops = Int(NoteLen/100)
                            For LoopIndex = 1 to NumLoops
                                Dummy        = Note[LoopIndex * 100, 'F ']
                                Loc          = Col2()
                                Note[Loc, 0] = CRLF$
                            Next LoopIndex
                            ServiceNotesFormatted<0, NoteIndex> = Note : CRLF$
                        end
                    Next NoteIndex
                    
                    ServiceNotes = ServiceNotesFormatted
                    
                    Swap @VM with CRLF$ in ServiceNotes
                    RowToAdd <1,9>     = ServiceNotes
                    modeArray         := RowToAdd : @FM
                Repeat
                
            end    
            GoSub ClearCursors
            ClearSelect TARGET_ACTIVELIST$
            // Old Modes
            SelectSent = 'SELECT REACT_MODE WITH REACT_NO = ':QUOTE(ReactNo):' AND WITH START_DT > ':QUOTE(OConv(StartDt, 'D4/')):'  BY-DSND START_DTM '
            Set_Status(0)
            RList(SelectSent, TARGET_ACTIVELIST$, "", "", "")
            IF Get_Status(errCode) THEN
                ErrMsg(errCode)
                RETURN
            END
            
            IF @RecCount then
                EoF = 0
                NumKeys = @RecCount
                Cnt = 0
                
                Loop
                    ReadNext Mode Else EoF = 1
                until EoF
                    
                    RowToAdd           = ''
                    ServiceCategories  = XLATE('REACT_MODE', Mode, 'PROB_CAT_DESC', 'X')
                    swap @VM with @svm in ServiceCategories
                    ServiceDescs       = XLATE('REACT_MODE', Mode, 'SERV_DESC', 'X')
                    ReactorLogID       = XLATE('REACT_MODE', Mode, 'START_RL_ID', 'X')
                    StartDTM           = OCONV(XLATE('REACT_MODE', Mode, 'START_DTM', 'X'),'DT2/^HS')
                    ServiceNotes       = XLATE('REACTOR_LOG',ReactorLogID, REACTOR_LOG_NOTES$, 'X')
                    swap @VM with @svm in ServiceDescs
                    RowToAdd <1,1>     = StartDTM                       
                    RowToAdd <1,2>     = OCONV(XLATE('REACT_MODE', Mode,'ELAP_HRS','X'),'MD2,')
                    RowToAdd <1,3>     = OCONV(XLATE('REACT_MODE', Mode, 'MODE', 'X' ),'[REACT_MODE_CONV]')
                    RowToAdd <1,4>     = XLATE('REACT_MODE', Mode, 'START_NOTES', 'X')
                    RowToAdd <1,5>     = ServiceCategories
                    RowToAdd <1,6>     = XLATE('REACT_MODE', Mode, 'START_USER', 'X')
                    RowToAdd <1,7>     = ReactorLogID
                    RowToAdd <1,8>     = ServiceDescs
                    
                    ServiceNotesDtms  = Xlate('REACTOR_LOG', ReactorLogID, REACTOR_LOG_NOTES_DTMS$, 'X')
                    ServiceNotesUsers = Xlate('REACTOR_LOG', ReactorLogID, REACTOR_LOG_NOTES_USERS$, 'X')
                    
                    ServiceNotesFormatted  = ''
                    For each ServiceNote in ServiceNotes using @VM setting vPos
                        ServiceNotesFormatted<0, vPos> = OConv(ServiceNotesDtms<0, vPos>, 'DT2/^H') : ' - ' : OConv(ServiceNotesUsers<0, vPos>, '[CONV_XLATE,LSL_USERS*FIRST_LAST]') : ' - ' : ServiceNote
                    Next ServiceNote
                    
                    NumNotes = DCount(ServiceNotesFormatted, @VM)
                    // Wrap text using CRLF$
                    For NoteIndex = 1 to NumNotes
                        Note    = ServiceNotesFormatted<0, NoteIndex>
                        NoteLen = Len(Note)
                        If NoteLen GT 100 then
                            NumLoops = Int(NoteLen/100)
                            For LoopIndex = 1 to NumLoops
                                Dummy        = Note[LoopIndex * 100, 'F ']
                                Loc          = Col2()
                                Note[Loc, 0] = CRLF$
                            Next LoopIndex
                            ServiceNotesFormatted<0, NoteIndex> = Note : CRLF$
                        end
                    Next NoteIndex
                    
                    ServiceNotes = ServiceNotesFormatted
                    
                    Swap @VM with @SVM in ServiceNotes
                    RowToAdd <1,9>     = ServiceNotes
                    modeArray         := RowToAdd : @FM
                    
                    
                Repeat
                response = modeArray
            end
        end else
            Open 'REACT_MODE_NG' to ReactModeTable then
                OPEN 'DICT.REACT_MODE_NG' TO @DICT then
                    
                    //StartDt = OCONV(StartDt,'D4/')
                    SelectSent = 'SELECT REACT_MODE_NG WITH REACT_NO = ':QUOTE(ReactNo):' AND WITH START_DT > ':QUOTE(OConv(StartDt, 'D4/')):'  BY-DSND START_DTM '
                    Set_Status(0)
                    errCode = ''
                    RList(SelectSent, TARGET_ACTIVELIST$, "", "", "")
                    If Not(Get_Status(errCode)) then
                        @RecCount = 0
                        EOF       = False$
                        Loop
                            Readnext @ID else EOF = True$
                        While EOF EQ False$    
                            READO @RECORD FROM ReactModeTable,@ID then
                                @RecCount             += 1
                                StartDTM               = OCONV({START_DTM},'DT2/^HS')
                                StopDTM                = OCONV({STOP_DTM},'DT2/^HS')
                                ElapsedHrs             = OCONV({ELAP_HRS},'MD2,')
                                Mode                   = OCONV({MODE},'[REACT_MODE_CONV]')
                                StartRLID              = {START_RL_ID}
                                ProbCatIDs             = {PROB_CAT_ID}
                                ProbCatDescs           = {PROB_CAT_DESC}
                                ServIDs                = {SERV_ID}
                                ServDescs              = {SERV_DESC}
                                StartUser              = {START_USER}
                                StopUser               = {STOP_USER}
                                Notes                  = {START_NOTES}
                                ServiceNotes           = XLATE('REACTOR_LOG', StartRLID, REACTOR_LOG_NOTES$, 'X')
                                ServiceNotesDtms       = Xlate('REACTOR_LOG', StartRLID, REACTOR_LOG_NOTES_DTMS$, 'X')
                                ServiceNotesUsers      = Xlate('REACTOR_LOG', StartRLID, REACTOR_LOG_NOTES_USERS$, 'X')
                                ServiceNotesFormatted  = ''
                                
                                For each ServiceNote in ServiceNotes using @VM setting vPos
                                    ServiceNotesFormatted<0, vPos> = OConv(ServiceNotesDtms<0, vPos>, 'DT2/^H') : ' - ' : OConv(ServiceNotesUsers<0, vPos>, '[CONV_XLATE,LSL_USERS*FIRST_LAST]') : ' - ' : ServiceNote
                                Next ServiceNote
                                
                                NumNotes = DCount(ServiceNotesFormatted, @VM)
                                // Wrap text using CRLF$
                                For NoteIndex = 1 to NumNotes
                                    Note    = ServiceNotesFormatted<0, NoteIndex>
                                    NoteLen = Len(Note)
                                    If NoteLen GT 100 then
                                        NumLoops = Int(NoteLen/100)
                                        For LoopIndex = 1 to NumLoops
                                            Dummy        = Note[LoopIndex * 100, 'F ']
                                            Loc          = Col2()
                                            Note[Loc, 0] = CRLF$
                                        Next LoopIndex
                                        ServiceNotesFormatted<0, NoteIndex> = Note : CRLF$
                                    end
                                Next NoteIndex
                                
                                ServiceNotes = ServiceNotesFormatted    
                                
                                FOR I = 1 TO COUNT(ProbCatIDs,@VM) + (ProbCatIDs NE '')
                                    ProbCatDescs<1,I> = ProbCatIDs<1,I>:' - ':ProbCatDescs<1,I>
                                NEXT I
                                
                                FOR I = 1 TO COUNT(ServIDs,@VM) + (ServIDs NE '')
                                    ServDescs<1,I> = ServIDs<1,I>:' - ':ServDescs<1,I>
                                NEXT I
                                
                                SWAP @VM WITH CRLF$ IN ProbCatDescs
                                SWAP @VM WITH CRLF$ IN ServDescs
                                Swap @VM with CRLF$ in ServiceNotes
                                
                                Response<@RecCount, 1> = StartDTM                        
                                Response<@RecCount, 2> = ElapsedHrs
                                Response<@RecCount, 3> = Mode
                                Response<@RecCount, 4> = Notes
                                Response<@RecCount, 5> = ProbCatDescs
                                Response<@RecCount, 6> = StartUser
                                Response<@RecCount, 7> = StartRLID
                                Response<@RecCount, 8> = ServDescs
                                Response<@RecCount, 9> = ServiceNotes
                            end
                        Repeat
                    end else
                        ErrMsg = 'Error in service ':Service:'. Error code: ':errCode:'.'
                        Error_Services('Add', ErrorMsg)          
                    end
                end else
                    ErrorMsg = 'Unable to Open "DICT.REACT_MODE_NG" table!'
                    Error_Services('Add', ErrorMsg)            
                end
            end else
                ErrorMsg = 'Unable to Open "REACT_MODE_NG" table!'
                Error_Services('Add', ErrorMsg)
            end
            
        end
    end
    
end service


Service GetReactorCassLoadHistory(ReactNo, DaysToReport)
    
    Response = ''
    If ( (ReactNo NE '') and (DaysToReport NE '') ) then
        Open 'RDS' to RDSTable then
            OPEN 'DICT.RDS' TO @DICT then
                StartDt    = Date() - DaysToReport
                SelectSent = 'SELECT RDS WITH REACTOR = ':QUOTE(ReactNo):' AND WITH DATE_IN > ':QUOTE(OConv(StartDt, 'D4/')):'  BY-DSND DATETIME_IN '
                Set_Status(0)
                errCode    = ''
                RList(SelectSent, TARGET_ACTIVELIST$, "", "", "")
                If Not(Get_Status(errCode)) then
                    @RecCount = 0
                    EOF       = False$
                    Loop
                        Readnext @ID else EOF = True$
                    While EOF EQ False$    
                        READO @RECORD FROM RDSTable,@ID then
                            @RecCount      += 1
                            RDSNo           = {SEQ}
                            DateIn          = OCONV({DATETIME_IN},'DT2/^HS')
                            DateOut         = OCONV({DATETIME_OUT},'DT2/^HS')
                            LoadOperator    = {OPERATOR_IN}
                            UnLoadOperator  = {OPERATOR_OUT}
                            
                            Response<@RecCount, 1> = RDSNo 
                            Response<@RecCount, 2> = DateIn                        
                            Response<@RecCount, 3> = DateOut
                            Response<@RecCount, 4> = LoadOperator
                            Response<@RecCount, 5> = UnLoadOperator
                        end
                    Repeat
                end else
                    ErrMsg = 'Error in service ':Service:'. Error code: ':errCode:'.'
                    Error_Services('Add', ErrorMsg)          
                end
            end else
                ErrorMsg = 'Unable to Open "DICT.RDS" table!'
                Error_Services('Add', ErrorMsg)            
            end
        end else
            ErrorMsg = 'Unable to Open "RDS" table!'
            Error_Services('Add', ErrorMsg)
        end
    end
    
end service



Service GetReactorCassLoadHistoryRange(ReactNo, StartDTM, StopDTM)
    
    Response = ''
    If ( (ReactNo NE '') and (StartDTM NE '') and (StopDTM NE '')) then
        Open 'RDS' to RDSTable then
            OPEN 'DICT.RDS' TO @DICT then
                SelectSent = 'SELECT RDS WITH REACTOR EQ ':ReactNo:' AND WITH DATETIME_IN GE ':StartDTM:' AND WITH DATETIME_IN LE ':StopDTM:' BY-DSND DATETIME_IN '
                Set_Status(0)
                errCode    = ''
                RList(SelectSent, TARGET_ACTIVELIST$, "", "", "")
                If Not(Get_Status(errCode)) then
                    @RecCount = 0
                    EOF       = False$
                    Loop
                        Readnext @ID else EOF = True$
                    While EOF EQ False$    
                        READO @RECORD FROM RDSTable,@ID then
                            @RecCount      += 1
                            RDSNo           = {SEQ}
                            DateIn          = OCONV({DATETIME_IN},'DT2/^HS')
                            DateOut         = OCONV({DATETIME_OUT},'DT2/^HS')
                            LoadOperator    = {OPERATOR_IN}
                            UnLoadOperator  = {OPERATOR_OUT}
                            
                            Response<@RecCount, 1> = RDSNo 
                            Response<@RecCount, 2> = DateIn                        
                            Response<@RecCount, 3> = DateOut
                            Response<@RecCount, 4> = LoadOperator
                            Response<@RecCount, 5> = UnLoadOperator
                        end
                    Repeat
                end else
                    ErrMsg = 'Error in service ':Service:'. Error code: ':errCode:'.'
                    Error_Services('Add', ErrorMsg)          
                end
            end else
                ErrorMsg = 'Unable to Open "DICT.RDS" table!'
                Error_Services('Add', ErrorMsg)            
            end
        end else
            ErrorMsg = 'Unable to Open "RDS" table!'
            Error_Services('Add', ErrorMsg)
        end
    end
    
end service


Service IncrementCycleCount(ToolID)  
    
    If ToolID NE '' then
        // A reactor number may be passed in. Convert it to the corresponding tool ID if necessary.
        If Num(ToolID) then ToolID = 'R':ToolID
        ToolRec                   = Database_Services('ReadDataRow', 'TOOL', ToolID)
        If Error_Services('NoError') then
            CycleCnt                  = ToolRec<TOOL_CYCLE_CNT$>
            CycleCnt                 += 1
            ToolRec<TOOL_CYCLE_CNT$>  = CycleCnt
            Database_Services('WriteDataRow', 'TOOL', ToolID, ToolRec, True$, False$, True$)
        end
    end
    
end service


Service CreateReactModeChange(UserID, ReactNo, Mode, ModeSubCat, ModeText, ForceModeChange)

	LogPath     = Environment_Services('GetApplicationRootPath') : '\LogFiles\ReactorModeChg'
	LogDate     = Oconv(Date(), 'D4/')
	LogTime     = Oconv(Time(), 'MTS')
	LogFileName = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' Mode Change Log.csv'
	Headers     = 'Logging DTM' : @FM : 'Notes'
	objLog      = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, ' ', Headers, '', False$, False$, True$)    
    LoggingDTM  = LogDate : ' ' : LogTime   ; // Logging DTM
    
    ErrorMsg           = ''
    IF ForceModeChange NE True$ then ForceModeChange = False$
    If ( (UserID NE '') and (ReactNo NE '') and (Mode NE '') and (ModeSubCat NE '')) then
        CurTime          = Time()
        CurDate          = Date()
        NGPrevModeKey    = Xlate('REACTOR_CHILD_KEY_IDS_NG', ReactNo, REACTOR_CHILD_KEY_IDS_REACT_MODE_KEY_IDS$, 'X')<1, 1>
        PrevModeRec      = Reactor_Services('GetReactCurrModeRec', ReactNo)
        SelectedModeRec  = Database_Services('ReadDataRow', 'REACTOR_MODES', Mode)
        SelE10State      = SelectedModeRec<REACTOR_MODES_E10_STATE$>
        RlComment        = ''
        CommentException = False$
        
        Begin Case
            Case    PrevModeRec<REACT_MODE_NG_MODE$> EQ 'WAITING_FOR_MAINTENANCE_UNSCHEDULED' AND Mode EQ 'MAINTENANCE_UNSCHEDULED'
                CommentException = True$
                
            Case    PrevModeRec<REACT_MODE_NG_MODE$> EQ 'WAITING_FOR_MAINTENANCE_SCHEDULED' AND Mode EQ 'MAINTENANCE_SCHEDULED'
                CommentException = True$
                
            Case    PrevModeRec<REACT_MODE_NG_MODE$> EQ 'WAITING_FOR_ENGINEER_UNSCHEDULED' AND Mode EQ 'ENGINEERING_INVESTIGATION'
                CommentException = True$
            
            Case    PrevModeRec<REACT_MODE_NG_MODE$> EQ 'WAITING_FOR_ENGINEER_SCHEDULED' AND Mode EQ 'ENGINEERING_DEVELOPMENT'
                CommentException = True$
            
            Case    PrevModeRec<REACT_MODE_NG_MODE$> EQ 'WAITING_FOR_ENGTECH_UNSCHEDULED' AND (Mode EQ 'TROUBLESHOOT_ENGTECH' OR Mode EQ 'ENGINEERING_INVESTIGATION')
                CommentException = True$
            
            Case    PrevModeRec<REACT_MODE_NG_MODE$> EQ 'WAITING_FOR_ENGTECH_SCHEDULED' AND Mode EQ 'ENGINEERING_DEVELOPMENT'
                CommentException = True$
                
        End Case
        If ((ModeText NE '') and (Len(ModeText) GE 4)) OR CommentException then
            
            NewModeCategory = SelectedModeRec<REACTOR_MODES_MODE_CATEGORY$>
            OldRlRecordKey  = PrevModeRec<REACT_MODE_NG_START_RL_ID$>
            If Reactor_Log_Services('IsMaint', OldRlRecordKey) AND ForceModeChange EQ False$ then
                If (NOT(Reactor_Log_Services('IsSigned', OldRlRecordKey))) then
                    ErrorMsg = 'The current mode cannot be changed until the technician has signed off on the Reactor Log.'
                end
            end
            
            ModeSubCatID = ''
            Locate ModeSubCat in SelectedModeRec<REACTOR_MODES_SERVICE_CATEGORIES$> using @VM setting SvcPos then
                NewModeSvcList = SelectedModeRec<REACTOR_MODES_SERVICE_CATEGORY_ID$>
                ModeSubCatID   = NewModeSvcList<1,SvcPos>
            end else
                ErrorMsg = 'Failed to locate ModeSubCat.'
            end
			
			CurrentMode = PrevModeRec<REACT_MODE_NG_MODE$>
			ActiveProveInPreventsModeChange = Reactor_Services('ActiveProveInPreventsModeChange', ReactNo, Mode, CurrentMode)
			
			If Error_Services('HasError') then
				ErrorMsg = Error_Services('GetMessage')
			end else
				If ActiveProveInPreventsModeChange EQ True$ then
					ErrorMsg = 'A reactor prove in is required before changing the mode. Active prove in types: '
					
					TypeList     = ''
					ProveInTypes = Reactor_Services('GetActiveProveInTypes', ReactNo)
					for each ProveInType in ProveInTypes using @VM setting Idx
						If Idx GT 1 then
							TypeList := ', '
						end
						TypeList := ProveInType
					Next ProveInType
					
					ErrorMsg := TypeList
				end
			end
			
			IdleStartupRequired = Reactor_Services('GetIdleStartupRequired', ReactNo)
			If ErrorMsg EQ '' and IdleStartupRequired and Mode[1, 2] EQ 'UP' then
				ProveInTypes = Reactor_Services('GetActiveProveInTypes', ReactNo)
				IdleStartupActive = False$
				For each ProveInType in ProveInTypes using @VM setting Idx
					If ProveInType EQ 'IDLE' then
						IdleStartupActive = True$
					end
				Next ProveInType
				
				If IdleStartupActive EQ True$ then
					ErrorMsg = "You must complete the IDLE_STARTUP prove in checklist before putting the reactor UP."
				end else
					ErrorMsg = "An IDLE_STARTUP prove in checklist is required before putting the reactor UP. Change the mode to IDLE_STARTUP, then complete the checklist."
				end
			end
			
			If ErrorMsg EQ '' then
				ActiveHgCVChecklists = Nica_Orders_Services('GetActiveOrders', 'REACTOR', ReactNo, 'IQS_HGCV_ALARM')
				HgCVChecklistActive  = (ActiveHgCVChecklists NE '') 				
				If ( HgCVChecklistActive and (SelE10State _EQC 'Productive') ) then
					ErrorMsg = "An HgCV OCAP Checklist is active for this reactor and must be completed or overridden in order to change to a productive state."
				end
			end
			
			If ErrorMsg EQ '' then
				IntrMaintFeatureFlag = Xlate('FEATURE_FLAGS', 'NICA_INTRUSIVE_MAINTENANCE', FEATURE_FLAGS.ENABLED$, 'X')
				If IntrMaintFeatureFlag EQ True$ then 
					ActiveIntrusiveMaintChecklists = Nica_Orders_Services('GetActiveOrders', 'REACTOR', ReactNo, 'INTRUSIVE_MAINT')
					IntrusiveMaintChecklistActive  = (ActiveIntrusiveMaintChecklists NE '')
					If ( IntrusiveMaintChecklistActive and (SelE10State _EQC 'Productive') ) then
						ErrorMsg = "An Intrusive Maintenance Checklist is active for this reactor and must be completed or overridden in order to change to a productive state."
					end
				end
			end
			
			If ErrorMsg EQ '' then
			    AbortAlarmFeatureFlag = Xlate('FEATURE_FLAGS', 'NICA_ABORT_ALARM',  FEATURE_FLAGS.ENABLED$, 'X')
			    If AbortAlarmFeatureFlag EQ True$ then
                    If IntrMaintFeatureFlag EQ True$ then
                        AbortAlarmFromIntrusiveMaintRequired = Nica_Orders_Services('AbortAlarmFromIntrusiveMaintRequired', ReactNo)
                        ActiveIntrusiveMaintChecklists       = Nica_Orders_Services('GetActiveOrders', 'REACTOR', ReactNo, 'INTRUSIVE_MAINT')
                        IntrusiveMaintChecklistActive        = (ActiveIntrusiveMaintChecklists NE '')                        
                    end else
                        AbortAlarmFromIntrusiveMaintRequired = False$
                        IntrusiveMaintChecklistActive        = False$
                    end
			        ModeSubCatFlowId       = Xlate('REACT_PROB_CAT', ModeSubCatId, REACT_PROB_CAT_ABORT_ALARM_FLOW_ID$, 'X')
			        ModeSubCatRespLvl      = Xlate('REACT_PROB_CAT', ModeSubCatId, REACT_PROB_CAT_ABORT_ALARM_RESPONSE_LEVEL$, 'X')
			        AbortAlarmComp         = Xlate('REACTOR', ReactNo, REACTOR_ABORT_ALARM_COMPLETE$, 'X')
			        ActiveAbortAlarmOrders = Nica_Orders_Services('GetActiveOrders', 'REACTOR', ReactNo, 'ABORT_ALARM')
			        AbortAlarmOrderActive  = (ActiveAbortAlarmOrders NE '')
                    ReactorType            = Xlate('REACTOR', ReactNo, REACTOR_REACT_TYPE$, 'X')
                    ChecklistIds           = Nica_Orders_Services('GetFlowChecklistIds', ModeSubCatFlowId, 'ABORT_ALARM', ReactorType)			        
			        CreateAbortAlarmOrder  = False$
			        Intrusive              = False$
			        
			        Begin Case
			            Case ( AbortAlarmOrderActive and (SelE10State _EQC 'Productive') )
			                ErrorMsg = "An ABORT/ALARM Checklist is active for this reactor and must be completed or overridden in order to change to a productive state."
			                
			            Case ( AbortAlarmFromIntrusiveMaintRequired and IndexC(ModeSubCat, 'ABORT/ALARM', 1) and (ModeSubCatFlowId NE '') and (ChecklistIds NE '') )
			                // INTRUSIVE_MAINT FIRST_PRODUCT_RUN_THICK or FIRST_PRODUCT_RUN_HGCV checklist started and
			                // ABORT/ALARM mode entered, so create an ABORT/ALARM order to contain product in reactor.
			                CreateAbortAlarmOrder = True$
			                Intrusive             = True$
                            
			            Case ( Not(IntrusiveMaintChecklistActive) and AbortAlarmOrderActive and IndexC(ModeSubCat, 'ABORT/ALARM', 1) and (ModeSubCatFlowId NE '') )
                            // If there is an active ABORT/ALARM NICA order, then we need to check if the new sub mode is different than the sub mode entered when the ABORT/ALARM
                            // NICA order was created and is of a higher response level or the new mode is WAITING_FOR_OPERATOR and the sub mode has changed. This means the maintenance
                            // team deemed the true ABORT/ALARM root cause was different than the cause the operators selected.
                            ActiveAbortAlarmFlowIds  = Xlate('NICA_ORDERS', ActiveAbortAlarmOrders, NICA_ORDERS.ORDER_FLOW_IDS$, 'X')
                            ActiveAbortAlarmRespLvls = Xlate('NICA_ORDERS', ActiveAbortAlarmOrders, NICA_ORDERS.ORDER_RESPONSE_LEVEL$, 'X')
                            MaxActiveRespLvl         = ''
                            For each ActiveAbortAlarmRespLvl in ActiveAbortAlarmRespLvls using @VM
                                MaxActiveRespLvl = Max(MaxActiveRespLvl, ActiveAbortAlarmRespLvl)
                            Next ActiveAbortAlarmRespLvl
                            If ( (Mode _EQC 'WAITING_FOR_OPERATOR') and (ModeSubCatFlowId NE ActiveAbortAlarmFlowIds) ) |
                            or ( (ModeSubCatFlowId NE ActiveAbortAlarmFlowIds) and (ModeSubCatRespLvl GT MaxActiveRespLvl) ) then
								If ChecklistIds NE '' then 			                
									CreateAbortAlarmOrder = True$
								end                            	
                            end

			            Case ( Not(IntrusiveMaintChecklistActive) and Not(AbortAlarmComp) and Not(AbortAlarmOrderActive) and IndexC(ModeSubCat, 'ABORT/ALARM', 1) and (ModeSubCatFlowId NE '') and (ChecklistIds NE '') )
			                // Typical ABORT/ALARM case
			                CreateAbortAlarmOrder = True$
			                
			            Case Otherwise$
			                // Don't create an ABORT/ALARM NICA order
			                Null
			                
			        End Case
			        
			        If CreateAbortAlarmOrder then
                        Nica_Orders_Services('CreateNewOrder', 'REACTOR', ReactNo, 'ABORT_ALARM', ModeSubCatFlowId, ModeSubCatRespLvl, ChecklistIds, Intrusive)
                        RlComment = 'ABORT/ALARM initiated for flow ':ModeSubCatFlowId:'.'					            
			        end
			        
			    end
			end
			
			If ErrorMsg EQ '' then
				If Len(Mode) GT 10 and Mode[1, 10] EQ 'CHANGEOVER' then
					Nica_Orders_Services('CreateNewOrder', 'REACTOR', ReactNo, 'CHANGEOVER')
				end
				
				If Mode EQ 'INITIATE_IDLE' then
					Nica_Orders_Services('CreateNewOrder', 'REACTOR', ReactNo, 'INITIATE_IDLE')
				end
				
				If Mode EQ 'IDLE_STARTUP' then
					Nica_Orders_Services('CreateNewOrder', 'REACTOR', ReactNo, 'IDLE')
				end
            end
            
            If ErrorMsg EQ '' then
                // Log the current Mode Change
                LogData     = ''
                LogData<1>  = Oconv(Date(), 'D4/') : ' ' : Oconv(Time(), 'MTS') ; // Logging DTM
                LogData<2>  = 'Reactor No: ' : ReactNo : ' - Requested Mode Change: ' : Mode
                Logging_Services('AppendLog', objLog, LogData, @RM, @FM, False$)   
                
                OldReactUtilID = PrevModeRec<REACT_MODE_NG_START_RU_ID$>
                
                if OldReactUtilID NE '' then
                    OldReactUtilRec                               = Database_Services('ReadDataRow', 'REACT_UTIL', OldReactUtilID)
                    OldReactUtilRec<react_util_end_date$>         = CurDate
                    OldReactUtilRec<react_util_end_time$>         = CurTime
                    OldReactUtilRec<react_util_mode_finish_user$> = UserId
                    Database_Services('WriteDataRow', 'REACT_UTIL', OldReactUtilID, OldReactUtilRec)
                end
                
                If Error_Services('NoError') then
                    **********************************************************************************
                    * WRITE OUT NEW MODE INFORMATION
                    
                    // Create new React_Util record
                    NewRUKey = NextKey('REACT_UTIL')
                    
                    If Error_Services('NoError') then
                        
                        NewRLKey = NextKey('REACTOR_LOG')
                        
                        If Error_Services('NoError') then
                            
                            RUtilRec                              = ''
                            RUtilRec<react_util_reactor$>         = ReactNo
                            RUtilRec<REACT_UTIL_NOTES$>           = ModeText
                            RMode                                 = ''
                            RUtilRec<react_util_mode$>            = Mode
                            RUtilRec<react_util_start_date$>      = CurDate
                            RUtilRec<react_util_start_time$>      = CurTime
                            RUtilRec<react_util_mode_start_user$> = UserID
                            RUtilRec<react_util_reactor_log_id$>  = NewRLKey
                            // Get current work order from the CONFIG table
                            DailySchedName = 'WO_DAILY_SCHED':ReactNo
                            DSR            = XLATE('CONFIG', DailySchedName, '', 'X')
                            WOCust         = DSR<WOCust$>
                            WorkOrder      = Field(WOCust, ' ', 1)
                            RUtilRec<react_util_wo$>              = WorkOrder
                            RUtilRec<react_util_cust_no$>         = xlate( 'WO_LOG', WorkOrder, wo_log_cust_no$, 'X' )			;
                            ********************************************************************************************************
                            
                            // Create a new Reactor_Log Entry
                            EntryId = UserID
                            
                            RLRec = ''
                            RLRec<reactor_log_start_date$>    = CurDate
                            RLRec<reactor_log_start_time$>    = CurTime
                            RLRec<reactor_log_reactor$>       = ReactNo
                            if NewModeCategory EQ 'MAINTENANCE' then
                                RLRec<reactor_log_category$>      = 'M' ;* for maintenance
                            end
                            RLRec<reactor_log_entry_id$>          = UserID		
                            RLRec<reactor_log_entry_date$>        = CurDate
                            RLRec<reactor_log_react_util_id$>     = NewRUKey
                            RLRec<reactor_log_react_prob_cat_id$> = ModeSubCatID
                            
                            CurrWoNo                         = Xlate('CONFIG', 'WO_DAILY_SCHED':ReactNo, 'F2', 'X')
                            CurrWoNo                         = CurrWoNo[1, 'F ']
                            If CurrWoNo NE '' then
                                CurrPSNo = Xlate('WO_LOG', CurrWoNo, 'PS_NO', 'X')
                            end else
                                CurrPSNo = ''
                            end
                            RLRec<REACTOR_LOG_PROD_SPEC_NO$> = CurrPSNo
                            
                            Database_Services('WriteDataRow', 'REACTOR_LOG', NewRLKey, RLRec)
                            If Error_Services('NoError') then
                                Database_Services('WriteDataRow', 'REACT_UTIL', NewRUKey, RUtilRec)
                                If Error_Services('NoError') then
                                    Rec                = ''
                                    Rec<Mode$>         = Mode
                                    Rec<Username$>     = oconv( UserID, '[XLATE_CONV,LSL_USERS*FIRST_LAST]' )
                                    Rec<Date$>         = oconv( CurDate, 'D4/' )
                                    Rec<Time$>         = oconv( CurTime, 'MTH' )
                                    Rec<ReactUtilID$>  = NewRUKey
                                    Rec<ReactorLogID$> = NewRLKey
                                    Rec<Service$>      = ModeSubCat
                                    Rec<Note$>         = ModeText
                                    CurrDTM            = Rec<Date$>:" ":Rec<Time$>
                                    
                                    NGPrevModeDTM = Field(NGPrevModeKey, '*', 2)
                                    PrevModeKey   = NGPrevModeKey
                                    OldMode       = OCONV(XLATE('REACTOR',ReactNo,'CURR_MODE','X'),'[REACT_MODE_CONV]')
                                    OpenDTM       = FIELD(PrevModeKey,'*',2)
                                    OpenDTM       = OCONV(OpenDTM,'DT4/^S')
                                    Mode          = Rec<Mode$>
                                    RecName       = 'REACT_MODE':ReactNo
                                    
                                    * * * * * Changes by JCH on 11/5/2007 * * * * *
                                    ProdModes  = 'Production':@VM
                                    ProdModes := 'Production (incr sampling)':@VM
                                    ProdModes := 'UP_WITH_RESTRICTIONS':@VM
                                    ProdModes := 'UP':@VM
                                    ProdModes := 'UP_WITH_INCREASED_SAMPLING':@VM
                                    ProdModes := 'UP_WITH_INCREASED_SAMPLING_SAMPLING':@VM
                                    ProdModes := 'UP_WITH_INCREASED_SAMPLING_METROLOGY':@VM
                                    ProdModes := 'UP_NOT_RUNNING':@VM
                                    
                                    LogData     = ''
                                    LogData<1>  = Oconv(Date(), 'D4/') : ' ' : Oconv(Time(), 'MTS') ; // Logging DTM
                                    LogData<2>  = 'Reactor No: ' : ReactNo : ' - Current Mode: ' : OldMode : ' - New Mode: ' : Mode
                                    Logging_Services('AppendLog', objLog, LogData, @RM, @FM, False$)
                                    LOCATE OldMode IN ProdModes USING @VM SETTING DUMMY THEN
                                        
                                        LOCATE Mode IN ProdModes USING @VM SETTING DUMMY ELSE
                                            
                                            OutOfProdDTM = Rec<Date$>:' ':Rec<Time$>
                                            
                                            obj_React_Status('SetOutOfProdDTM',ReactNo:@RM:OutOfProdDTM)
                                            
                                            IF Get_Status(errCode) THEN ErrorMsg = errCode
                                            
                                        END		;* End of check on New Mode
                                    END		;* End of check on Current Mode
                                    
                                    * * * End of changes * * * * *
                                    // Write new mode info to config table.
                                    OrigConfigRec = Database_Services('ReadDataRow', 'CONFIG', RecName)
                                    Database_Services('WriteDataRow', 'CONFIG', RecName, Rec)
                                    
                                    If Error_Services('NoError') then
                                        IF OldMode NE '' THEN
                                            crParms  = ReactNo
                                            crParms := @RM:OpenDTM
                                            crParms := @RM:CurrDTM
                                            crParms := @RM:UserID
                                            crParms := @RM:Rec<Note$>
                                            crParms := @RM:Rec<ReactUtilID$>
                                            crParms := @RM:Rec<ReactorLogID$>
                                            LogData     = ''
                                            LogData<1>  = Oconv(Date(), 'D4/') : ' ' : Oconv(Time(), 'MTS') ; // Logging DTM
                                            LogData<2>  = 'Reactor No: ' : ReactNo : ' - obj_React_Mode("Close") - Key ID: ' : ReactNo : '*' : Iconv(OpenDTM, 'DT')
                                            Logging_Services('AppendLog', objLog, LogData, @RM, @FM, False$)
                                            obj_React_Mode('Close',crParms)				;* Close the currently active mode
                                        END
                                        If Not(Get_Status(errCode)) then
                                            
                                            crParms  = ReactNo
                                            crParms := @RM:CurrDTM
                                            crParms := @RM:UserID
                                            crParms := @RM:Mode
                                            crParms := @RM:Rec<Note$>
                                            crParms := @RM:Rec<ReactUtilID$>
                                            crParms := @RM:Rec<ReactorLogID$>
                                            crParms := @RM:ModeSubCat
                                            
                                            LogData     = ''
                                            LogData<1>  = Oconv(Date(), 'D4/') : ' ' : Oconv(Time(), 'MTS') ; // Logging DTM
                                            LogData<2>  = 'Reactor No: ' : ReactNo : ' - obj_React_Mode("Create") - Key ID: ' : ReactNo : '*' : Iconv(CurrDTM, 'DT')
                                            Logging_Services('AppendLog', objLog, LogData, @RM, @FM, False$)
                                            
                                            obj_React_Mode('Create',crParms)		;* Create new active mode record
                                            
                                            IF Get_Status(errCode) THEN
                                                // Failed to create new react mode
                                                ErrorMsg         = errCode
                                                GoSub RollbackReactUtil
                                                // Delete new REACTOR_LOG record
                                                Database_Services('DeleteDataRow', 'REACTOR_LOG', NewRLKey)
                                                // Delete new REACT_UTIL record
                                                Database_Services('DeleteDataRow', 'REACT_UTIL', NewRUKey)
                                                // Rollback CONFIG record
                                                Database_Services('WriteDataRow', 'CONFIG', RecName, OrigConfigRec)
                                                // Reopen previous react mode
                                                Database_Services('WriteDataRow', 'REACT_MODE_NG', NGPrevModeKey, PrevModeRec)
                                            end else
                                            	If RlComment NE '' then Reactor_Log_Services('AddComment', ReactNo, RlComment, UserID)        
                                            	If SelE10State _EQC 'Productive' then
                                            		// Reset ABORT/ALARM flag
													Open 'REACTOR' to hReactor then
														WriteV False$ to hReactor, ReactNo, REACTOR_ABORT_ALARM_COMPLETE$ then
															LogData    = ''
															LogData<1> = Oconv(Date(), 'D4/') : ' ' : Oconv(Time(), 'MTS') ; // Logging DTM
															LogData<2> = 'Successfully set ABORT_ALARM_COMPLETE flag to false for Reactor No: ' : ReactNo
															Logging_Services('AppendLog', objLog, LogData, @RM, @FM, False$)															
														end else
															LogData    = ''
															LogData<1> = Oconv(Date(), 'D4/') : ' ' : Oconv(Time(), 'MTS') ; // Logging DTM
															LogData<2> = 'Failed set ABORT_ALARM_COMPLETE flag to false for Reactor No: ' : ReactNo
															Logging_Services('AppendLog', objLog, LogData, @RM, @FM, False$)															
														end
													end                                            		
                                            	end
                                            	
                                                // Check to see if notifications are active for this mode
                                                If SelectedModeRec<REACTOR_MODES_NOTIFICATION_ACTIVE$> then
                                                    //If notifications are active get the classes from the REACTOR_MODE record
                                                    SendToClasses = SelectedModeRec<REACTOR_MODES_NOTIFY_CLASS$>
                                                    For each class in SendToClasses using @VM
                                                        //Send to all the classes
                                                        notesParms       = ''
                                                        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
                                                        Recipients    = LSL_USERS_SERVICES('GetOnShiftUsersByClass', class, IncludeNextShift); *Get all the on shift recipients
                                                        SentFrom      = 'OI_ADMIN'
                                                        Subject       = 'Reactor Mode Notification - ': ReactNo : ' ' : Mode
                                                        Message       = 'Reactor: ' :ReactNo: ' has been placed into ' : Mode :'.'
                                                        Message      := CRLF$ : 'Sub-Category: ' : ModeSubCat
                                                        Message      := CRLF$ : 'User: ' : UserID
                                                        Message      := CRLF$ : 'Notes: '
                                                        Message      := CRLF$ : ModeText
                                                        AttachWindow  = ''
                                                        AttachKey     = ''
                                                        SendToGroup   = ''
                                                        notesParms    = Recipients:@RM:SentFrom:@RM:Subject:@RM:Message:@RM:AttachWindow:@RM:AttachKey:@RM:SendToGroup
                                                        Obj_Notes('Create', notesParms)
                                                    Next class
                                                end
                                                
                                                // If new mode sub category is an ABORT/ALARM or particular Facilities Failure, then apply abort metrology to the oldest loaded RDS
												
												ReactType = Xlate('REACTOR', ReactNo, 'REACT_TYPE', 'X')
												If ReactType NE 'EPP' then 
													Begin Case
														Case ModeSubCat _EQC 'ABORT/ALARM - TC Failure'
															ApplyAbortMet = True$
														Case ModeSubCat _EQC 'ABORT/ALARM - Contactor Not Sensed On'
															ApplyAbortMet = True$
														Case ModeSubCat _EQC 'ABORT/ALARM - Temp Up Failure'
															ApplyAbortMet = True$
														Case ModeSubCat _EQC 'Gas Delivery Issue'
															ApplyAbortMet = True$
														Case ModeSubCat _EQC 'Toxic Gas Alarm'
															ApplyAbortMet = True$
														Case ModeSubCat _EQC 'ABORT/ALARM - Facilities Level Failure'
															ApplyAbortMet = True$
														Case ModeSubCat _EQC 'ABORT/ALARM - Exhaust System Failure (Scrubber Toxic)'
															ApplyAbortMet = True$
														Case ModeSubCat _EQC 'ABORT/ALARM - Gas Flow - TCS'
															ApplyAbortMet = True$
														Case ModeSubCat _EQC 'ABORT/ALARM - Gas Flow - Dopant'
															ApplyAbortMet = True$
														Case ModeSubCat _EQC 'ABORT/ALARM - Gas Flow - Hydrogen'
															ApplyAbortMet = True$
														Case ModeSubCat _EQC 'ABORT/ALARM - Susceptor Faults'
															ApplyAbortMet = True$
														Case ModeSubCat _EQC 'ABORT/ALARM - 24V. Interlock Abort'
															ApplyAbortMet = True$
														Case ModeSubCat _EQC 'ABORT/ALARM - Wafer Handling'
															ApplyAbortMet = True$
														Case Otherwise$
															ApplyAbortMet = False$
													End Case
													
													If ApplyAbortMet then
															
														LogPath     = Environment_Services('GetApplicationRootPath') : '\LogFiles\ReactorAborts'
														LogDate     = Oconv(Date(), 'D4/')
														LogTime     = Oconv(Time(), 'MTS')
														LogFileName = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' Reactor Abort Log.csv'
														Headers     = 'Logging DTM' : @FM : 'ReactorNo' : @FM : 'Notes'
														objAbortLog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, ' ', Headers, '', False$, False$, True$) 
														LoggingDTM  = LogDate : ' ' : LogTime   ; // Logging DTM
														
														LogMsg      = 'Intermediate or Major ABORT/ALARM mode sub category, ':ModeSubCat:'. '
														LogMsg     := 'Attempting to apply abort metrology to loaded RDS.'
														LogData     = ''
														LogData<1>  = LoggingDtm
														LogData<2>  = ReactNo
														LogData<3>  = LogMsg
														Logging_Services('AppendLog', objAbortLog, LogData, @RM, @FM)
														
														// Get the loaded RDS key(s) and apply abort metrology. If two are loaded, select the one loaded first.
														LoadedRdsKeys = Xlate('REACT_STATUS', ReactNo, 'LOAD_RDS', 'X')
														If LoadedRdsKeys NE '' then
															
															AbortRds = ''
															
															Begin Case
																Case DCount(LoadedRdsKeys, @VM) EQ 2
																	// Select the oldest one
																	LoadDtms = Xlate('RDS', LoadedRdsKeys, 'DATETIME_IN', 'X')
																	If LoadDtms<0, 2> GT LoadDtms<0, 1> then
																		AbortRds = LoadedRdsKeys<0, 1>
																	end else
																		AbortRds = LoadedRdsKeys<0, 2>
																	end
																	
																	Swap @VM with ' ' in LoadedRdsKeys
																	LogMsg     = 'Two loaded RDS keys, ':LoadedRdsKeys:', found in REACT_STATUS record.'
																	LogData    = ''
																	LogData<1> = LoggingDtm
																	LogData<2> = ReactNo
																	LogData<3> = LogMsg
																	Logging_Services('AppendLog', objAbortLog, LogData, @RM, @FM)
																	
																	LogMsg     = 'Selecting RDS ':AbortRDS:' as it was loaded earlier.'
																	LogData    = ''
																	LogData<1> = LoggingDtm
																	LogData<2> = ReactNo
																	LogData<3> = LogMsg
																	Logging_Services('AppendLog', objAbortLog, LogData, @RM, @FM)  
																	
																Case DCount(LoadedRdsKeys, @VM) EQ 1
																	// Only one loaded, so use that
																	AbortRds   = LoadedRdsKeys
																	LogMsg     = 'One loaded RDS key, ':AbortRds:', found in REACT_STATUS record.'
																	LogData    = ''
																	LogData<1> = LoggingDtm
																	LogData<2> = ReactNo
																	LogData<3> = LogMsg
																	Logging_Services('AppendLog', objAbortLog, LogData, @RM, @FM)                                                 				
																	
																Case Otherwise$
																	// Unexpected condition - log error
																	Swap @VM with ' ' in LoadedRdsKeys
																	LogMsg     = 'Error selecting RDS to apply abort metrology to. Loaded RDS keys, ':LoadedRdsKeys:', found in REACT_STATUS record.'
																	LogData    = ''
																	LogData<1> = LoggingDtm
																	LogData<2> = ReactNo
																	LogData<3> = LogMsg
																	Logging_Services('AppendLog', objAbortLog, LogData, @RM, @FM) 
																	
															End Case
															
															If AbortRds NE '' then
																LogMsg     = 'Attempting to apply abort metrology to RDS ':AbortRds:'.'
																LogData    = ''
																LogData<1> = LoggingDtm
																LogData<2> = ReactNo
																LogData<3> = LogMsg
																Logging_Services('AppendLog', objAbortLog, LogData, @RM, @FM)
																
																Rds_Services('ApplyAbortMetrology', AbortRds)
																If Error_Services('NoError') then

																	LogMsg     = 'Successfully applied abort metrology to RDS ':AbortRds:'.'
																	LogData    = ''
																	LogData<1> = LoggingDtm
																	LogData<2> = ReactNo
																	LogData<3> = LogMsg
																	Logging_Services('AppendLog', objAbortLog, LogData, @RM, @FM) 

																end else
																	
																	ErrorMsg = Error_Services('GetMessage')

																	LogMsg     = 'Error occurred while applying abort metrology to RDS ':AbortRds:'. Error message: ':ErrorMsg
																	LogData    = ''
																	LogData<1> = LoggingDtm
																	LogData<2> = ReactNo
																	LogData<3> = LogMsg
																	Logging_Services('AppendLog', objAbortLog, LogData, @RM, @FM) 
																	
																end
															end
															
														end else
															// No loaded RDS runs detected
															LogMsg     = 'No loaded RDS keys found in REACT_STATUS record.'
															LogData    = ''
															LogData<1> = LoggingDtm
															LogData<2> = ReactNo
															LogData<3> = LogMsg
															Logging_Services('AppendLog', objAbortLog, LogData, @RM, @FM)                                                		
														end
													end
                                                end
                                            end
                                        end else
                                            // Failed to close previous react mode
                                            ErrorMsg = errCode
                                            GoSub RollbackReactUtil
                                            // Delete new REACTOR_LOG record
                                            Database_Services('DeleteDataRow', 'REACTOR_LOG', NewRLKey)
                                            // Delete new REACT_UTIL record
                                            Database_Services('DeleteDataRow', 'REACT_UTIL', NewRUKey)
                                            // Rollback CONFIG record
                                            Database_Services('WriteDataRow', 'CONFIG', RecName, OrigConfigRec)
                                        end
                                    END ELSE
                                        ErrorMsg   = 'Reactor No: ' : ReactNo : 'Unable to write ':RecName:' on CONFIG table...' : ReactNo : '*' : Iconv(CurrDTM, 'DT')
                                        LogData    = ''
                                        LogData<1> = Oconv(Date(), 'D4/') : ' ' : Oconv(Time(), 'MTS') ; // Logging DTM
                                        LogData<2> = ErrorMsg
                                        Logging_Services('AppendLog', objLog, LogData, @RM, @FM, False$)
                                        GoSub RollbackReactUtil
                                        // Delete new REACTOR_LOG record
                                        Database_Services('DeleteDataRow', 'REACTOR_LOG', NewRLKey)
                                        // Delete new REACT_UTIL record
                                        Database_Services('DeleteDataRow', 'REACT_UTIL', NewRUKey)
                                    end
                                end else
                                    // Failed to write REACT_UTIL record
                                    ErrorMsg = Error_Services('GetMessage')
                                    GoSub RollbackReactUtil
                                    // Delete new REACTOR_LOG record
                                    Database_Services('DeleteDataRow', 'REACTOR_LOG', NewRLKey)
                                end
                            end else
                                // Failed to write REACTOR_LOG record
                                ErrorMsg = Error_Services('GetMessage')
                                GoSub RollbackReactUtil
                            end
                        end else
                            // Failed to get new REACTOR_LOG key
                            ErrorMsg = Error_Services('GetMessage')
                            GoSub RollbackReactUtil
                        end
                    end else
                        // Failed to get new REACT_UTIL key
                        ErrorMsg = Error_Services('GetMessage')
                        GoSub RollbackReactUtil
                    end
                end else
                    ErrorMsg = Error_Services('GetMessage')
                end
            end
        end else
           Begin Case
               Case ModeText EQ ''
                ErrorMsg = 'You must enter a comment.'
                
                Case Len(ModeText) LT 5
                ErrorMsg = 'Comments must be more than 4 characters...'
           End Case
        end
         
    end else
        
        Begin Case
            
            Case UserID EQ ''
                ErrorMsg = 'Null UserID passed into service.'
                
            Case ReactNo EQ ''
                ErrorMsg = 'You must choose a Reactor.'
                
            Case Mode EQ ''
                ErrorMsg = 'You must choose a VALID Reactor Mode.'
                
            Case ModeSubCat EQ ''
                ErrorMsg = 'You must choose a VALID Reactor Problem Category.'
  
        End Case
    end
    
    If ErrorMsg EQ '' then
        Response = True$
    end else
        Error_Services('Add', 'Error in ':Service:' service. ':ErrorMsg)
        Response = False$
    end
    
end service


Service FormatReactStatusSingle(ReactNo)
	
	Response     = ''
	CurrModeRec  = Reactor_Services('GetReactCurrModeRec', ReactNo)
	CurrLoadings = Reactor_Services('GetReactorCurrLoad', ReactNo)
	
	ReactStatusArray     = CurrModeRec
	ReactStatusArray<12> = CurrLoadings; *Currently loaded cassettes
	
	Response = ReactStatusArray
	
end service


Service GetSingleReactStatusJson(reactNo)
    
    response = ''
    // Create the root object JSON
    If SRP_Json(objReactor, "New") then
        // Create the array of reactors
        If SRP_Json(objReactorArray, "New", "Array") then
            ReactStatusArray = Reactor_Services('FormatReactStatusSingle', reactNo)
            CurrE10State  = Xlate('REACTOR_MODES', ReactStatusArray<REACT_MODE_NG_MODE$>, REACTOR_MODES_E10_STATE$, 'X')
            ModeIconName  = Xlate('REACTOR_MODES', ReactStatusArray<REACT_MODE_NG_MODE$>, REACTOR_MODES_ICON$, 'X')
            RunningStatus = false$
            CurrLoad      = Reactor_Services('GetReactorCurrLoad', ReactNo)
            CurrSubCat    = ReactStatusArray<REACT_MODE_NG_SERVICE_DESC$>
            if CurrLoad NE '' then
                RunningStatus = true$
            end
            objSingleReactor = ''
            If SRP_Json(objSingleReactor, "New") then
                SRP_JSON(objSingleReactor, 'SETVALUE', 'ReactorNumber', reactNo, 'STRING')
                SRP_JSON(objSingleReactor, 'SETVALUE', 'ReactorMode', ReactStatusArray<REACT_MODE_NG_MODE$>, 'STRING')
                SRP_JSON(objSingleReactor, 'SETVALUE', 'E10State', CurrE10State, 'STRING')
                SRP_JSON(objSingleReactor, 'SETVALUE', 'ModeIconName', ModeIconName, 'STRING')
                SRP_JSON(objSingleReactor, 'SETVALUE', 'IsRunning', RunningStatus, 'BOOLEAN')
                SRP_JSON(objSingleReactor, 'SETVALUE', 'ModeSubCat', CurrSubCat, 'STRING')
                SRP_Json(objReactorArray, "Add", objSingleReactor)
                SRP_Json(objSingleReactor, "Release")
            end
            // Now add the array as a member of the root object
            SRP_Json(objReactor, "Set", "reactors", objReactorArray)
            
            // All done with the array object
            SRP_Json(objReactorArray, "Release")
        end
        // Now get the actual JSON
        resultingJSON = SRP_Json(objReactor, "Stringify", "STYLED")
        
        // All done with the root object
        SRP_Json(objReactor, "Release")
    end

    response = resultingJSON
    
end service


Service GetAllReactStatusJson()
    
    Response = ''
    
    AllReactNos = Reactor_Services('GetReactorNumbers')
    
    //Create the root objec JSON
    If SRP_Json(objReactor, "New") then
        //Create the array of reactors
        If SRP_Json(objReactorArray, "New", "Array") then
            //Loop through all reactor nums and add to the array
            for each reactNo in AllReactNos using @FM
                if reactNo NE 'Cln' OR reactNo NE '140' then
                    ReactStatusArray = Reactor_Services('FormatReactStatusSingle', reactNo)
                    CurrE10State     = Xlate('REACTOR_MODES', ReactStatusArray<REACT_MODE_NG_MODE$>, REACTOR_MODES_E10_STATE$, 'X')
                    ModeIconName     = Xlate('REACTOR_MODES', ReactStatusArray<REACT_MODE_NG_MODE$>, REACTOR_MODES_ICON$, 'X')
                    RunningStatus    = false$
                    CurrLoad         = Reactor_Services('GetReactorCurrLoad', ReactNo)
                    if CurrLoad NE '' then
                        RunningStatus = true$
                    end
                    If SRP_Json(objSingleReactor, "New") then
                        SRP_JSON(objSingleReactor, 'SETVALUE', 'ReactorNumber', reactNo, 'STRING')
                        SRP_JSON(objSingleReactor, 'SETVALUE', 'ReactorMode', ReactStatusArray<REACT_MODE_NG_MODE$>, 'STRING')
                        SRP_JSON(objSingleReactor, 'SETVALUE', 'E10State', CurrE10State, 'STRING')
                        SRP_JSON(objSingleReactor, 'SETVALUE', 'ModeIconName', ModeIconName, 'STRING')
                        SRP_JSON(objSingleReactor, 'SETVALUE', 'IsRunning', RunningStatus, 'BOOLEAN')
                        SRP_Json(objReactorArray, "Add", objSingleReactor)
                        SRP_Json(objSingleReactor, "Release")
                    end
                end
                
                
            Next reactNo
            // Now add the array as a member of the root object
            SRP_Json(objReactor, "Set", "reactors", objReactorArray)
            
            // All done with the array object
            SRP_Json(objReactorArray, "Release")
        end
        // Now get the actual JSON
        resultingJSON = SRP_Json(objReactor, "Stringify", "STYLED")
        
        // All done with the root object
        SRP_Json(objReactor, "Release")
    end

    response = resultingJSON
    
end service


Service IsReactorLoaded(ReactNo)
	
	ErrorMsg = ''
	Response = False$
	If ReactNo NE '' then
		LoadedRDS = Xlate('REACT_STATUS', ReactNo, 'LOAD_RDS', 'X')
		If LoadedRDS NE '' then Response = True$
	end else
		ErrorMsg = 'Error in ':Service:' service. Null ReactNo passed in.'
	end
	If ErrorMsg NE '' then
		Error_Services('Add', ErrorMsg)
	end
	
end service


Service GetReactorTimeSinceUnload(ReactNo)
	
	ErrorMsg = ''
	Response = 0
	
	If ReactNo NE '' then
		LastUnloadTime = Xlate('REACT_STATUS', ReactNo, 'UNLOAD_DTM', 'X')
		
		Response = SRP_DateTime("MinuteSpan", LastUnloadTime, Datetime(), 0)
	end else
		ErrorMsg = 'Error in ':Service:' service. Null ReactNo passed in.'
	end
	
	If ErrorMsg NE '' then
		Error_Services('Add', ErrorMsg)
	end
	
end service


Service ConvertRecordToJSON(ReactorNo, ItemURL, CurrUser, FullObject=BOOLEAN)
	
	If FullObject EQ '' then FullObject = True$
	ErrorMsg = ''
	JSON     = ''
	If ReactorNo then
		objJSON = ''
		If SRP_JSON(objJSON, 'New', 'Object') then
			// Add in reactor current mode, available modes, and available sub categories.
			CurrModeKey = Reactor_Services('GetReactCurrModeId', ReactorNo)
			CurrMode    = Xlate('REACT_MODE_NG', CurrModeKey, REACT_MODE_NG_MODE$, 'X')
			ServiceDesc = Xlate('REACT_MODE_NG', CurrModeKey, REACT_MODE_NG_SERVICE_DESC$, 'X')
			If (CurrUser NE '') then AvailModes = Reactor_Modes_Services('AvailableModes', CurrMode, CurrUser)
			CurrRLKey   = Xlate('REACT_MODE_NG', CurrModeKey, REACT_MODE_NG_START_RL_ID$ , 'X')
			ReactorType = Xlate('REACTOR', ReactorNo, 'REACT_TYPE', 'X')
			ReactAssign = Xlate('REACTOR', ReactorNo, 'REACT_ASSIGNMENT', 'X')
			React_Assign_Conv('OCONV', ReactAssign, '', ReactAssign)
			// Create reactor object
			If SRP_JSON(objReactor, 'New', 'Object') then
				SRP_JSON(objReactor, 'SetValue', 'reactorNo', ReactorNo)
				SRP_JSON(objReactor, 'SetValue', 'reactType', ReactorType)
				SRP_JSON(objReactor, 'SetValue', 'reactAssignment', ReactAssign)
				PickPlace     = Xlate('REACTOR', ReactorNo, 'PICK_PLACE', 'X')
				SRP_JSON(objReactor, 'SetValue', 'pickPlace', PickPlace, 'Boolean')
				LLDisabledKey = Xlate('REACTOR', ReactorNo, 'ACTIVE_LL_DISABLED', 'X')
				LoadLockDisabled = Xlate('REACT_LL', LLDisabledKey, 'DISABLED', 'X')
				SRP_JSON(objReactor, 'SetValue', 'loadLockDown', LoadLockDisabled)
				SRP_JSON(objReactor, 'SetValue', '0311Active', Xlate('REACTOR', ReactorNo, '0311_ACTIVE', 'X'), 'Boolean')
				SRP_JSON(objReactor, 'SetValue', 'active0311Checklist', Xlate('REACTOR', ReactorNo, '0311_ACTIVE', 'X'), 'Boolean')
				SRP_JSON(objReactor, 'SetValue', 'TubeBellJarCnt', Xlate('REACTOR', ReactorNo, REACTOR_TUBE_BELL_JAR_THK$, 'X'))
				SRP_JSON(objReactor, 'SetValue', 'TubeBellJarThk', Xlate('REACTOR', ReactorNo, REACTOR_TUBE_BELL_JAR_WFR_CNT$, 'X'))
				SRP_JSON(objReactor, 'SetValue', 'SusceptorCnt', Xlate('REACTOR', ReactorNo, REACTOR_SUSC_WFR_CNT$, 'X'))
				SRP_JSON(objReactor, 'SetValue', 'SusceptorThk', Xlate('REACTOR', ReactorNo, REACTOR_SUSC_THK$, 'X'))
				SRP_JSON(objReactor, 'SetValue', 'LowerQuartzCnt', Xlate('REACTOR', ReactorNo, REACTOR_LOWER_QUARTZ_WFR$, 'X'))
				SRP_JSON(objReactor, 'SetValue', 'LowerQuartzThk', Xlate('REACTOR', ReactorNo, REACTOR_LOWER_QUARTZ_THK$, 'X'))
				SRP_JSON(objReactor, 'SetValue', 'ArmsCnt', Xlate('REACTOR', ReactorNo, REACTOR_ARMS_WFR_CNT$, 'X'))
				
				// Add Current Run Status (Current Loaded Cassettes)
				LoadedRDS   = Xlate('REACT_STATUS', ReactorNo, 'LOAD_RDS', 'X')
				If LoadedRDS NE '' then
					objLoadedRDS = ''
					If SRP_JSON(objLoadedRDS, 'New', 'Array') then
						For each RDS in LoadedRDS using @VM setting vPos
							SRP_JSON(objLoadedRDS, 'AddValue', RDS)
						Next RDS
						SRP_JSON(objReactor, 'Set', 'loadedRDS', objLoadedRDS)
						SRP_JSON(objLoadedRDS, 'Release')
					end
				end else
					SRP_JSON(objReactor, 'SetValue', 'loadedRDS', '')
				end
				
				// Add new reactor items here
				InstItems = Xlate('REACTOR', ReactorNo, REACTOR_CURR_INST_ITEMS$, 'X')
				If InstItems NE '' then
					objInstItems = ''
					If SRP_JSON(objInstItems, 'New', 'Array') then
						For each Item in InstItems using @VM setting vPos
							swap '*' with @VM in Item
							SRP_JSON(objInstItems, 'AddValue', Item<1, 2>)
						Next Item
						SRP_JSON(objReactor, 'Set', 'installedReactItems', objInstItems)
						SRP_JSON(objInstItems, 'Release')
					end
				end else
					SRP_JSON(objReactor, 'SetValue', 'installedReactItems', '')
				end
				
				// Add Current Run Status (Is running)
				ReactorRunning = (LoadedRDS NE '')
				SRP_JSON(objReactor, 'SetValue', 'isRunning', ReactorRunning, 'Boolean')
				
				// Add Out of prod time
				OutofProdDTM = Xlate('REACT_STATUS', ReactorNo, 'OUT_OF_PROD_DTM', 'X')
				SRP_JSON(objReactor, 'SetValue', 'outOfProdDTM', OConv(OutofProdDTM, 'DT2/^H'))
				
				If FullObject then
					// Add Last Unload Time
					LastUnloadDTM = Xlate('REACT_STATUS', ReactorNo, 'UNLOAD_RDS_DTM', 'X')
					SRP_JSON(objReactor, 'SetValue', 'unloadDTM', OConv(LastUnloadDTM, 'DT2/^H'))
					
					// Add Last Load Time
					LastLoadDTM = Xlate('REACT_STATUS', ReactorNo, 'LOAD_RDS_DTM', 'X')
					LastLoadDTM = LastLoadDTM[-1, 'B':@VM]
					SRP_JSON(objReactor, 'SetValue', 'loadDTM', OConv(LastLoadDTM, 'DT2/^H'))
					
					// Add Last Reactor Readings
					LastReadWfrsDTM	= Xlate('REACTOR', ReactorNo, 'LAST_READ_WFRS_DTM', 'X')
					LastReadWfrs	= Xlate('REACT_READS', ReactorNo:'*':LastReadWfrsDTM, 'WAFER_CNT', 'X')
					SRP_JSON(objReactor, 'SetValue', 'lastReadWafersDTM', OConv(LastReadWfrsDTM, 'DT2/^H'))
					SRP_JSON(objReactor, 'SetValue', 'lastReadWafers', LastReadWfrs)
				end
				
				// Add work order object
				SchedConfigRec = Database_Services('ReadDataRow', 'CONFIG', 'WO_DAILY_SCHED':ReactorNo)
				WorkOrderCust  = SchedConfigRec<WOCust$>
				CurrWorkOrder  = Field(WorkOrderCust, ' ', 1)
				SRP_JSON(objReactor, 'SetValue', 'workOrder', CurrWorkOrder)
				WOHotStatus    = Xlate('WO_LOG', CurrWorkOrder, 'HOT_FLAG', 'X')
				SRP_JSON(objReactor, 'SetValue', 'workOrderHotStatus', WOHotStatus)
				WOCustomer     = Field(WorkOrderCust, ' ', 2)
				SRP_JSON(objReactor, 'SetValue', 'workOrderCustomer', WOCustomer)
				If FullObject then
					JSONWorkOrder = Work_Order_Services('ConvertRecordToJSON', CurrWorkOrder)
					objWorkOrder  = '' 
					If SRP_JSON(objWorkOrder, 'Parse', JSONWorkOrder) EQ '' then
						objTemp = SRP_JSON(objWorkOrder, 'Get', 'workOrder')
						SRP_JSON(objReactor, 'Set', 'workOrder', objTemp)
						SRP_JSON(objTemp, 'Release')
						SRP_JSON(objWorkOrder, 'Release')
					end
				end
				
				// Reactor Mode object/properties
				If FullObject then
					// Add reactor mode object
					JSONReactorMode = React_Mode_NG_Services('ConvertRecordToJSON', CurrModeKey, '', CurrUser)
					objReactorMode  = ''
					If SRP_JSON(objReactorMode, 'Parse', JSONReactorMode) EQ '' then
						objTemp = SRP_JSON(objReactorMode, 'Get', 'reactorMode')
						SRP_JSON(objReactor, 'Set', 'reactorMode', objTemp)
						SRP_JSON(objTemp, 'Release')
						SRP_JSON(objReactorMode, 'Release')
					end
				end else
					CurrMode = Xlate('REACT_MODE_NG', CurrModeKey, 'MODE', 'X')
					SubCat   = Xlate('REACT_MODE_NG', CurrModeKey, 'SERVICE_DESC', 'X')
					E10State = Xlate('REACTOR_MODES', CurrMode, 'E10_STATE', 'X')
					SRP_JSON(objReactor, 'SetValue', 'currMode', CurrMode)
					SRP_JSON(objReactor, 'SetValue', 'serviceDesc', SubCat)
					SRP_JSON(objReactor, 'SetValue', 'e10State', E10State)
				end
				
				If Not(FullObject) then
					// Add reactor log properties
					CurrRlKey = Xlate('REACTOR', ReactorNo, 'CURR_MODE_RL_ID', 'X')
					If CurrRLKey NE '' then
						RlChecklistActive   = Xlate('REACTOR_LOG', CurrRlKey, 'CHECKLIST_ACTIVE', 'X')
						SRP_JSON(objReactor, 'SetValue', 'reactorLogChecklistActive', RlChecklistActive, 'Boolean')
						If RlChecklistActive then
							RlChecklistTypes    = Xlate('REACTOR_LOG', CurrRlKey, 'CHECKLIST_TYPE', 'X')
							RlChecklistOrderIds = Xlate('REACTOR_LOG', CurrRlKey, 'CHECKLIST_ORDER_ID', 'X')
							objRlChecklistArray = ''
							If SRP_JSON(objRlChecklistArray, 'New', 'Array') then
								objChecklist = ''
								For each RlChecklistType in RlChecklistTypes using @VM setting vPos
									If SRP_JSON(objChecklist, 'New', 'Object') then
										SRP_JSON(objChecklist, 'SetValue', 'reactorLogChecklistType', RlChecklistType)
										RlChecklistOrderId = RlChecklistOrderIds<0, vPos>
										SRP_JSON(objChecklist, 'SetValue', 'reactorLogChecklistOrderId', RlChecklistOrderId)
										SRP_JSON(objRlChecklistArray, 'Add', objChecklist)
										SRP_JSON(objChecklist, 'Release')
									end
								Next RlChecklistType
								SRP_JSON(objReactor, 'Set', 'reactorLogChecklists', objRlChecklistArray)
								SRP_JSON(objRlChecklistArray, 'Release')
							end
						end
					end
					// Add prove in checklist properties
					PiChecklistActive = Xlate('REACTOR', ReactorNo, 'PROVE_IN_ACTIVE', 'X')
					SRP_JSON(objReactor, 'SetValue', 'proveInChecklistActive', PiChecklistActive, 'Boolean')
					If PiChecklistActive then
						PiTypes             = Xlate('REACTOR', ReactorNo, 'PROVE_IN_TYPE', 'X')
						PiChecklistOrderIds = Xlate('REACTOR', ReactorNo, 'PROVE_IN_ORDER_ID', 'X')
						objPiChecklistArray = ''
						If SRP_JSON(objPiChecklistArray, 'New', 'Array') then
							objProveIn = ''
							For each PiType in PiTypes using @VM setting vPos
								If SRP_JSON(objProveIn, 'New', 'Object') then
									SRP_JSON(objProveIn, 'SetValue', 'proveInType', PiType)
									PiChecklistOrderId = PiCheckListOrderIds<0, vPos>
									SRP_JSON(objProveIn, 'SetValue', 'proveInOrderId', PiChecklistOrderId)
									SRP_JSON(objPiChecklistArray, 'Add', objProveIn)
									SRP_JSON(objProveIn, 'Release')
								end
							Next PiType
							SRP_JSON(objReactor, 'Set', 'proveInChecklists', objPiChecklistArray)
							SRP_JSON(objPiChecklistArray)
						end
					end
				end
				
				If itemURL NE '' then
					// The itemURL was passed in so add HAL+JSON properties.
					objLinks = ''
					// Create the _links property and then all link objects needed for this resource.
					If SRP_JSON(objLinks, 'New', 'Object') then
						// Create a self link.
						objLink = ''
						If SRP_JSON(objLink, 'New', 'Object') then
							SRP_JSON(objLink, 'SetValue', 'href', ItemURL, 'String')
							SRP_JSON(objLink, 'SetValue', 'title', 'Self', 'String')
							SRP_JSON(objLinks, 'Set', 'self', objLink)
							SRP_JSON(objLink, 'Release')
						end
						SRP_JSON(objReactor, 'Set', '_links', objLinks)
						SRP_JSON(objLinks, 'Release')
					end
					// Create the _class property for this resource.
					SRP_JSON(objReactor, 'SetValue', '_class', 'resource')
				end
				SRP_JSON(objJSON, 'Set', 'reactor', objReactor)
				SRP_JSON(objReactor, 'Release')
			end
			JSON = SRP_JSON(objJSON, 'Stringify', 'Styled')
			SRP_JSON(objJSON, 'Release')
		end else
			ErrorMsg = 'Error in ':Service:' service. Error creating JSON object.'
		end
	end else
		ErrorMsg = 'Error in ':Service:' service. Null ReactNo passed in.'
	end
	
	If ErrorMsg EQ '' then
		Response = JSON
	end else
		Error_Services('Add', ErrorMsg)
	end
	
end service


Service ConvertJSONToRecord(KeyID, JSON)
	
	Record = ''
	If JSON NE '' then
		If SRP_JSON(objJSON, 'Parse', JSON) EQ '' then
			objReactor = SRP_JSON(objJSON, 'Get', 'reactor')
			Record     = Database_Services('ReadDataRow', 'REACTOR', KeyID)
			If Error_Services('NoError') then
				// Add code to update additional columns as needed.
				// If property is "" then clear the field.
				// If property is null then do not update it.
				NewVal                       = SRP_JSON(objReactor, 'GetValue', '0311Active')
				If NewVal NE 'null' then
					Record<REACTOR_0311_ACTIVE$> = NewVal
				end
			end
			SRP_JSON(objReactor, 'Release')
			SRP_JSON(objJSON, 'Release')
		end else
			Error_Services('Add', 'Error in ':Service:' service. Unable to parse JSON payload.')
		end
	end else
		Error_Services('Add', 'Error in ':Service:' service. Null JSON passed in.')
	end
	
	Response = Record
	
end service


Service CleanReactStatus()
	
	hSysLists = Database_Services('GetTableHandle', 'SYSLISTS')
	Lock hSysLists, ServiceKeyID then
		ReactNos  = Reactor_Services('GetReactorNumbers')
		For each ReactNo in ReactNos using @FM
			HaveLock         = Database_Services('GetKeyIDLock', 'REACT_STATUS', ReactNo)
			If HaveLock then
				OrigReactStatRec = Database_Services('ReadDataRow', 'REACT_STATUS', ReactNo)
				ReactStatRec     = OrigReactStatRec
				If Error_Services('NoError') then
					For Col = 1 to 9
						OrigList = OrigReactStatRec<Col>
						If OrigList NE '' then
							CleanList = ''
							For each Val in OrigList using @VM
								If Val NE 1 then CleanList<0, -1> = Val
							Next Val
							ReactStatRec<Col> = CleanList
						end
					Next Col
					If OrigReactStatRec NE ReactStatRec then
						Database_Services('WriteDataRow', 'REACT_STATUS', ReactNo, ReactStatRec, True$, False$, False$)
					end else
						Database_Services('ReleaseKeyIDLock', 'REACT_STATUS', ReactNo)
					end
				end
			end
		Next ReactNo
		Unlock hSysLists, ServiceKeyID else Null
	end
	
end service


Service GetReactModeKeysByTimeSpan(ReactorNo, StartDtm, StopDtm)
	
    Begin Case
        Case ReactorNo Eq ''
            Error_Services('Add', 'Error in ' : Service : ' service. ReactorNo not supplied.')
        Case StartDtm Eq ''
            Error_Services('Add', 'Error in ' : Service : ' service. StartDtm not supplied.')
        Case StopDtm Eq ''
            Error_Services('Add', 'Error in ' : Service : ' service. StopDtm not supplied.')
    End Case
    If Error_Services('NoError') then
        //Get all modes within the StartDtm and StopDtm
        StartDt = FIELD(StartDtm, '.', 1)
        StopDt  = Field(StopDtm, '.', 1)
        keys    = ''
        OPEN 'DICT.REACT_MODE_NG' TO @DICT then
            SearchString  = ''
            SearchString := "START_DTM" :@VM: StartDtm : '~' : StopDtm: @FM
            SearchString := "STOP_DTM" :@VM: ';' : StartDtm : '~' : StopDtm : @FM
            SearchString := "REACT_NO" :@VM: ReactorNo : @FM
            keylist       = ""
            option        = ""
            flag          = ""
            Btree.Extract(SearchString, 'REACT_MODE_NG', @DICT, keylist, option, flag)
            for each key in keylist
                keys<-1> = key
            Next key
        end
        if keys<1> EQ '' then
            //Get Current Mode because it started before report date and has yet to finish
            //Is the report time for the current mode?
            CurrReactorModeKey      = Reactor_Services('GetReactCurrModeId', ReactorNo)
            CurrReactorModeStartDtm = Field(CurrReactorModeKey, '*', 2)
            IF StopDtm GT CurrReactorModeStartDtm then
                keys<-1> = CurrReactorModeKey
            end else
                //
                SearchString  = ''
                SearchString := "START_DTM" :@VM: '<=' : StartDtm: @FM
                SearchString := "STOP_DTM" :@VM: '>=' : StopDtm : @FM
                SearchString := "REACT_NO" :@VM: ReactorNo : @FM
                SubKeyList    = ""
                option        = ""
                flag          = ""
                Btree.Extract(SearchString, 'REACT_MODE_NG', @DICT, SubKeyList, option, flag)
                for each SubKey in SubKeyList
                    keys<-1> = SubKey
                Next SubKey
            end
            
            
            //Is the report time outside of the current mode
        end
        Response = keys
    end else
        Response = Error_Services('GetMessage')
    end
    
end service



Service GetReactorUptimeMetricsByTimeSpan(ReactorNo, StartDtm, StopDtm)
	
    ToolData                     = ''
    TotalProductiveMinutes      = 0
    TotalUnscheduledDownMinutes = 0
    TotalScheduledDownMinutes   = 0
    TotalNonSchedMinutes   = 0
    TotalEngMinutes = 0
    
    Begin Case
        Case ReactorNo Eq ''
            Error_Services('Add', 'Error in ' : Service : ' service. ReactorNo not supplied.')
        Case StartDtm Eq ''
            Error_Services('Add', 'Error in ' : Service : ' service. StartDtm not supplied.')
        Case StopDtm Eq ''
            Error_Services('Add', 'Error in ' : Service : ' service. StopDtm not supplied.')
    End Case
    //If StopDtm is in future then set it to current time.
    If StopDtm GT Datetime()  then
        StopDtm = Datetime()
    end
    If Error_Services('NoError') then
        
        //Get Keys for the supplied time span
        ReactModeNGKeys    = Reactor_Services('GetReactModeKeysByTimeSpan', ReactorNo, StartDtm, StopDtm)
        If Error_Services('NoError') then
            //Get total minutes of timespan.
            TotalReportMinutes = SRP_Datetime('MinuteSpan', StartDtm, StopDtm)
            //Loop Through each mode key returned
            
            for each key in ReactModeNGKeys using @VM
                ReactModeNGRec = Database_Services('ReadDataRow', 'REACT_MODE_NG', key)
                ModeStartDtm   = Field(key, '*', 2)
                ModeStopDtm    = ReactModeNGRec<REACT_MODE_NG_STOP_DTM$>
                ReactModeName  = ReactModeNGRec<REACT_MODE_NG_MODE$>
                E10State       = Xlate('REACTOR_MODES', ReactModeName, REACTOR_MODES_E10_STATE$, 'X')
                MinutesInMode  = 0
                Begin Case
                    CASE E10State EQ 'Productive'
                        //Begin Production
                        if ModeStartDtm LT StartDtm then
                        //Mode Started Earlier than report time
                        If ModeStopDtm LT StopDtm then
                            //Mode finished within the timespan
                            If ModeStopDtm NE '' then
                                MinutesInMode = SRP_Datetime('MinuteSpan', StartDTM, ModeStopDtm)
                            end else
                                //Mode is not closed
                                 
                                If ModeStartDtm LT StartDtm then
                                    //if ModeStartDtm is before StartDtm and is not close used the total report timespan
                                    MinutesInMode = SRP_Datetime('MinuteSpan', StartDTM, StopDtm)
                                end else
                                    //Use the ModeStartDtm and the StopDtm
                                    MinutesInMode = SRP_Datetime('MinuteSpan', ModeStartDtm, StopDtm)
                                end
                            end
                        end else
                            //Mode finished after the timespan
                            MinutesInMode = SRP_Datetime('MinuteSpan', StartDTM, StopDtm)
                        end
                        end else
                            //Mode started after report time
                            If ModeStopDtm LT StopDtm then
                                //Mode finished within the timespan OR isn't yet closed
                                If ModeStopDtm NE '' then
                                    //Mode is closed. Started and finished within the report time so use the Mode's start and stop
                                    MinutesInMode = SRP_Datetime('MinuteSpan', ModeStartDtm, ModeStopDtm)
                                end else
                                    //Mode is not closed. Started within the report time but has yet to stop.
                                    MinutesInMode = SRP_Datetime('MinuteSpan', ModeStartDtm, StopDtm)
                                end
                            end else
                                //Mode finished after the timespan
                                MinutesInMode = SRP_Datetime('MinuteSpan', ModeStartDtm, StopDtm)
                            end
                        end
                        TotalProductiveMinutes += MinutesInMode
                    Case E10State EQ 'Unscheduled Down'
                                                //Begin Production
                        if ModeStartDtm LT StartDtm then
                        //Mode Started Earlier than report time
                        If ModeStopDtm LT StopDtm then
                            //Mode finished within the timespan
                            If ModeStopDtm NE '' then
                                MinutesInMode = SRP_Datetime('MinuteSpan', StartDTM, ModeStopDtm)
                            end else
                                //Mode is not closed
                                 
                                If ModeStartDtm LT StartDtm then
                                    //if ModeStartDtm is before StartDtm and is not close used the total report timespan
                                    MinutesInMode = SRP_Datetime('MinuteSpan', StartDTM, StopDtm)
                                end else
                                    //Use the ModeStartDtm and the StopDtm
                                    MinutesInMode = SRP_Datetime('MinuteSpan', ModeStartDtm, StopDtm)
                                end
                            end
                        end else
                            //Mode finished after the timespan
                            MinutesInMode = SRP_Datetime('MinuteSpan', StartDTM, StopDtm)
                        end
                        end else
                            //Mode started after report time
                            If ModeStopDtm LT StopDtm then
                                //Mode finished within the timespan OR isn't yet closed
                                If ModeStopDtm NE '' then
                                    //Mode is closed. Started and finished within the report time so use the Mode's start and stop
                                    MinutesInMode = SRP_Datetime('MinuteSpan', ModeStartDtm, ModeStopDtm)
                                end else
                                    //Mode is not closed. Started within the report time but has yet to stop.
                                    MinutesInMode = SRP_Datetime('MinuteSpan', ModeStartDtm, StopDtm)
                                end
                            end else
                                //Mode finished after the timespan
                                MinutesInMode = SRP_Datetime('MinuteSpan', ModeStartDtm, StopDtm)
                            end
                        end
                        TotalUnscheduledDownMinutes += MinutesInMode
                    Case E10State EQ 'Scheduled Down'
                                                                        //Begin Production
                        if ModeStartDtm LT StartDtm then
                        //Mode Started Earlier than report time
                        If ModeStopDtm LT StopDtm then
                            //Mode finished within the timespan
                            If ModeStopDtm NE '' then
                                MinutesInMode = SRP_Datetime('MinuteSpan', StartDTM, ModeStopDtm)
                            end else
                                //Mode is not closed
                                 
                                If ModeStartDtm LT StartDtm then
                                    //if ModeStartDtm is before StartDtm and is not close used the total report timespan
                                    MinutesInMode = SRP_Datetime('MinuteSpan', StartDTM, StopDtm)
                                end else
                                    //Use the ModeStartDtm and the StopDtm
                                    MinutesInMode = SRP_Datetime('MinuteSpan', ModeStartDtm, StopDtm)
                                end
                            end
                        end else
                            //Mode finished after the timespan
                            MinutesInMode = SRP_Datetime('MinuteSpan', StartDTM, StopDtm)
                        end
                        end else
                            //Mode started after report time
                            If ModeStopDtm LT StopDtm then
                                //Mode finished within the timespan OR isn't yet closed
                                If ModeStopDtm NE '' then
                                    //Mode is closed. Started and finished within the report time so use the Mode's start and stop
                                    MinutesInMode = SRP_Datetime('MinuteSpan', ModeStartDtm, ModeStopDtm)
                                end else
                                    //Mode is not closed. Started within the report time but has yet to stop.
                                    MinutesInMode = SRP_Datetime('MinuteSpan', ModeStartDtm, StopDtm)
                                end
                            end else
                                //Mode finished after the timespan
                                MinutesInMode = SRP_Datetime('MinuteSpan', ModeStartDtm, StopDtm)
                            end
                        end
                        TotalScheduledDownMinutes += MinutesInMode
                    Case E10State EQ 'Non-Scheduled' OR E10State EQ 'Standby'
                    	if ModeStartDtm LT StartDtm then
                        //Mode Started Earlier than report time
                        If ModeStopDtm LT StopDtm then
                            //Mode finished within the timespan
                            If ModeStopDtm NE '' then
                                MinutesInMode = SRP_Datetime('MinuteSpan', StartDTM, ModeStopDtm)
                            end else
                                //Mode is not closed
                                 
                                If ModeStartDtm LT StartDtm then
                                    //if ModeStartDtm is before StartDtm and is not close used the total report timespan
                                    MinutesInMode = SRP_Datetime('MinuteSpan', StartDTM, StopDtm)
                                end else
                                    //Use the ModeStartDtm and the StopDtm
                                    MinutesInMode = SRP_Datetime('MinuteSpan', ModeStartDtm, StopDtm)
                                end
                            end
                        end else
                            //Mode finished after the timespan
                            MinutesInMode = SRP_Datetime('MinuteSpan', StartDTM, StopDtm)
                        end
                        end else
                            //Mode started after report time
                            If ModeStopDtm LT StopDtm then
                                //Mode finished within the timespan OR isn't yet closed
                                If ModeStopDtm NE '' then
                                    //Mode is closed. Started and finished within the report time so use the Mode's start and stop
                                    MinutesInMode = SRP_Datetime('MinuteSpan', ModeStartDtm, ModeStopDtm)
                                end else
                                    //Mode is not closed. Started within the report time but has yet to stop.
                                    MinutesInMode = SRP_Datetime('MinuteSpan', ModeStartDtm, StopDtm)
                                end
                            end else
                                //Mode finished after the timespan
                                MinutesInMode = SRP_Datetime('MinuteSpan', ModeStartDtm, StopDtm)
                            end
                        end
                        TotalNonSchedMinutes += MinutesInMode
                	Case E10State EQ 'Engineering'
                    	if ModeStartDtm LT StartDtm then
                        //Mode Started Earlier than report time
                        If ModeStopDtm LT StopDtm then
                            //Mode finished within the timespan
                            If ModeStopDtm NE '' then
                                MinutesInMode = SRP_Datetime('MinuteSpan', StartDTM, ModeStopDtm)
                            end else
                                //Mode is not closed
                                 
                                If ModeStartDtm LT StartDtm then
                                    //if ModeStartDtm is before StartDtm and is not close used the total report timespan
                                    MinutesInMode = SRP_Datetime('MinuteSpan', StartDTM, StopDtm)
                                end else
                                    //Use the ModeStartDtm and the StopDtm
                                    MinutesInMode = SRP_Datetime('MinuteSpan', ModeStartDtm, StopDtm)
                                end
                            end
                        end else
                            //Mode finished after the timespan
                            MinutesInMode = SRP_Datetime('MinuteSpan', StartDTM, StopDtm)
                        end
                        end else
                            //Mode started after report time
                            If ModeStopDtm LT StopDtm then
                                //Mode finished within the timespan OR isn't yet closed
                                If ModeStopDtm NE '' then
                                    //Mode is closed. Started and finished within the report time so use the Mode's start and stop
                                    MinutesInMode = SRP_Datetime('MinuteSpan', ModeStartDtm, ModeStopDtm)
                                end else
                                    //Mode is not closed. Started within the report time but has yet to stop.
                                    MinutesInMode = SRP_Datetime('MinuteSpan', ModeStartDtm, StopDtm)
                                end
                            end else
                                //Mode finished after the timespan
                                MinutesInMode = SRP_Datetime('MinuteSpan', ModeStartDtm, StopDtm)
                            end
                        end
                        TotalEngMinutes += MinutesInMode
                End Case
                
            Next key
            ProdPercentage        = (TotalProductiveMinutes / TotalReportMinutes) * 100
            UnschedDownPercentage = (TotalUnscheduledDownMinutes / TotalReportMinutes) * 100
            SchedDownPercentage   = (TotalScheduledDownMinutes / TotalReportMinutes) * 100
            NonSchedPercentage = (TotalNonSchedMinutes / TotalReportMinutes) * 100
            EngPercentage = (TotalEngMinutes / TotalReportMinutes) * 100
            

            //Percentages
            ToolData<1, PRODUCTIVE$>  = ProdPercentage
            ToolData<1, UNSCHED$>     = UnschedDownPercentage
            ToolData<1, SCHED$>       = SchedDownPercentage
            ToolData<1, NONSCHED$>       = NonSchedPercentage
            ToolData<1, ENG$> = EngPercentage
            //Total Minutes
            ToolData<2, PRODUCTIVE$>  = TotalProductiveMinutes
            ToolData<2, UNSCHED$>     = TotalUnscheduledDownMinutes
            ToolData<2, SCHED$>       = TotalScheduledDownMinutes
            ToolData<2, NONSCHED$>    = TotalNonSchedMinutes
            ToolData<2, ENG$>		  = TotalEngMinutes
            Response                  = ToolData
        end else
            Response = Error_Services('GetMessage')
        end
    end else
        Response = Error_Services('GetMessage')
    end
    
end service


Service GetReactorUptimeMetricsByTypeAndTimeSpan(startDtm, endDtm, reactType)
	
	Begin Case
		Case startDtm EQ ''
			Error_Services('Add', 'Error getting reactor uptime metrics -> Report start date not supplied')
		Case endDtm EQ ''
			Error_Services('Add', 'Error getting reactor uptime metrics -> Report end date not supplied')
	End Case
	If reactType EQ '' then reactType = 'ALL'
	If Error_Services('NoError') then
		If endDtm GT Date() then
			//This sets the current report end time to the current time.
			endDtm = Datetime()
		end
		reportMinutes = SRP_Datetime('MinuteSpan', startDtm, endDtm)
		if num(reportMinutes) then
			Begin Case
				Case reactType EQ 'ASM' or reactType EQ 'ASM+'
					Reactors = Reactor_Services('GetReactorNumbers', 'ASM') : @FM : Reactor_Services('GetReactorNumbers', 'ASM+')
				Case reactType EQ 'HTR'
					Reactors = Reactor_Services('GetReactorNumbers', 'HTR')
				Case reactType EQ 'EPP'
					Reactors = Reactor_Services('GetReactorNumbers', 'EPP')
				Case reactType EQ 'ALL'
					Reactors = Reactor_Services('GetReactorNumbers', 'ASM') : @FM : Reactor_Services('GetReactorNumbers', 'ASM+') : @FM : Reactor_Services('GetReactorNumbers', 'HTR') : @FM : Reactor_Services('GetReactorNumbers', 'EPP')
			End Case
			AllReactorsScheduledMinutes = 0
			AllReactorsUptimeMinutes = 0
			AllReactorsProdMinutes = 0
			AllReactorsUnschedDownMinutes = 0
			AllReactorsSchedDownMinutes = 0
			AllReactorsEngMinutes = 0
			AllReactorsIdleMinutes = 0
			AllReactorsSingleLL= 0 
			TotalMinutesUp = 0
			ReactorsData = ''
			ReactorsExcluded = ''
			for each Reactor in Reactors using @FM
				ReactorStateTimes = Reactor_Services('GetReactorUptimeMetricsByTimeSpan', Reactor, startDtm, endDtm)
				PercentProd = ReactorStateTimes<1,PRODUCTIVE$>
				MinuteProd = ReactorStateTimes<2,PRODUCTIVE$>
				PercentUnSchedDown = ReactorStateTimes<1,UNSCHED$>
				MinuteUnschedDown = ReactorStateTimes<2,UNSCHED$>
				PercentSchedDown = ReactorStateTimes<1,SCHED$>
				MinuteSchedDown = ReactorStateTimes<2,SCHED$>
				PercentNonSched = ReactorStateTimes<1,NONSCHED$>
				MinutesNonSched = ReactorStateTimes<2,NONSCHED$>
				PercentEng = ReactorStateTimes<1, ENG$>
				MinutesEng = ReactorStateTimes<2, ENG$>
				if MinutesNonSched LT reportMinutes then
					thisReactorExpectedAvailMinutes = reportMinutes - MinutesNonSched; * This is the amount of minutes the single reactor was scheduled to run work.
					AllReactorsScheduledMinutes += thisReactorExpectedAvailMinutes; * Add it to the collective of reactors
					thisReactorUptimeMinutes = MinuteProd + MinutesEng
					AllReactorsUptimeMinutes = thisReactorUptimeMinutes + AllReactorsUptimeMinutes
					AllReactorsProdMinutes += MinuteProd
					AllReactorsUnschedDownMinutes += MinuteUnschedDown
					AllReactorsSchedDownMinutes += MinuteSchedDown
					AllReactorsEngMinutes += MinutesEng
					AllReactorsIdleMinutes += MinutesNonSched
					if reactType NE 'EPP' then
						thisReactorDisabledLoadLock = Reactor_Services('GetReactorDownLL', Reactor) NE ''
						AllReactorsSingleLL += thisReactorDisabledLoadLock
					end else
						thisReactorDisabledLoadLock = 0
					end
					ReactorsData<REACTOR_DAILY_UPTIME_INCL_REACTORS$,-1> = Reactor
					ReactorsData<REACTOR_DAILY_UPTIME_INCL_REACTOR_PROD_PERCENTAGE$,-1> = PercentProd
					ReactorsData<REACTOR_DAILY_UPTIME_INCL_REACTOR_PROD_MINUTES$,-1> = MinuteProd
					ReactorsData<REACTOR_DAILY_UPTIME_INCL_REACTOR_ENG_PERCENTAGE$, -1> = PercentEng
					ReactorsData<REACTOR_DAILY_UPTIME_INCL_REACTOR_ENG_MINUTES$, -1> = MinutesEng
					ReactorsData<REACTOR_DAILY_UPTIME_INCL_REACTOR_DOWN_UNSCHED_PERCENTAGE$, -1> = PercentUnSchedDown
					ReactorsData<REACTOR_DAILY_UPTIME_INCL_REACTOR_DOWN_UNSCHED_MINUTES$, -1> = MinuteUnschedDown
					ReactorsData<REACTOR_DAILY_UPTIME_INCL_REACTOR_DOWN_SCHED_PERCENTAGE$, -1> = PercentSchedDown
					ReactorsData<REACTOR_DAILY_UPTIME_INCL_REACTOR_DOWN_SCHED_MINUTES$, -1> = MinuteSchedDown
					ReactorsData<REACTOR_DAILY_UPTIME_INCL_REACTOR_IDLE_PERCENTAGE$, -1> = PercentNonSched
					ReactorsData<REACTOR_DAILY_UPTIME_INCL_REACTOR_IDLE_MINUTES$, -1> = MinutesNonSched
					ReactorsData<REACTOR_DAILY_UPTIME_INCL_REACTOR_SINGLE_LL$, -1> = thisReactorDisabledLoadLock
				end else
					ReactorsData<REACTOR_DAILY_UPTIME_EXCLUDED_REACTORS$, -1> = Reactor
				end
			Next Reactor
			if AllReactorsScheduledMinutes GT 0  then
				TotalUptimePercent = SRP_Math('ROUND', AllReactorsUptimeMinutes / AllReactorsScheduledMinutes, 5)
				TotalUptimeMinutes = SRP_Math('ROUND', AllReactorsUptimeMinutes, 5)
				TotalAvailMinutes = SRP_Math('ROUND', AllReactorsScheduledMinutes, 5)
				TotalProdPercent = SRP_Math('ROUND', AllReactorsProdMinutes / AllReactorsScheduledMinutes, 5)
				TotalUnschedDownPercent = SRP_Math('ROUND', AllReactorsUnschedDownMinutes / AllReactorsScheduledMinutes, 5)
				TotalSchedDownPercent = SRP_Math('ROUND', AllReactorsSchedDownMinutes / AllReactorsScheduledMinutes, 5)
				TotalEngPercent = SRP_Math('ROUND', AllReactorsEngMinutes / AllReactorsScheduledMinutes, 5)
				TotalIdlePercent = SRP_Math('ROUND', AllReactorsIdleMinutes / AllReactorsScheduledMinutes, 5)
			end else
				TotalUptimePercent = 0
				TotalUptimeMinutes = 0
				TotalAvailMinutes = 0
				TotalProdPercent = 0
				TotalUnschedDownPercent = 0
				TotalSchedDownPercent = 0
				TotalEngPercent = 0
				TotalIdlePercent = 0
			end
			ReactorsData<REACTOR_DAILY_UPTIME_TOTAL_UPTIME_PERCENTAGE$> = TotalUptimePercent
			ReactorsData<REACTOR_DAILY_UPTIME_TOTAL_PROD_PERCENTAGE$> = TotalProdPercent
			ReactorsData<REACTOR_DAILY_UPTIME_TOTAL_PROD_MINUTES$> = AllReactorsProdMinutes
			ReactorsData<REACTOR_DAILY_UPTIME_TOTAL_ENG_PERCENTAGE$> = TotalEngPercent
			ReactorsData<REACTOR_DAILY_UPTIME_TOTAL_ENG_MINUTES$> = AllReactorsEngMinutes
			ReactorsData<REACTOR_DAILY_UPTIME_TOTAL_DOWN_UNSCHED_PERCENTAGE$> = TotalUnschedDownPercent
			ReactorsData<REACTOR_DAILY_UPTIME_TOTAL_DOWN_UNSCHED_MINUTES$> = AllReactorsUnschedDownMinutes
			ReactorsData<REACTOR_DAILY_UPTIME_TOTAL_DOWN_SCHED_PERCENTAGE$> = TotalSchedDownPercent
			ReactorsData<REACTOR_DAILY_UPTIME_TOTAL_DOWN_SCHED_MINUTES$> = AllReactorsSchedDownMinutes
			ReactorsData<REACTOR_DAILY_UPTIME_TOTAL_IDLE_PERCENTAGE$> = TotalIdlePercent
			ReactorsData<REACTOR_DAILY_UPTIME_TOTAL_IDLE_MINUTES$> = AllReactorsIdleMinutes
			ReactorsData<REACTOR_DAILY_UPTIME_TOTAL_UPTIME_MINUTES$> = TotalUptimeMinutes
			ReactorsData<REACTOR_DAILY_UPTIME_TOTAL_AVAIL_MINUTES$> = TotalAvailMinutes
			ReactorsData<REACTOR_DAILY_UPTIME_TOTAL_SINGLE_LL$> = AllReactorsSingleLL
			Response = ReactorsData
		end else
			Error_Services('Add', 'Error getting reactor uptime metrics -> Unable to get total report duration.')
		end
	end
	
end service


Service GetLoadedRds(ReactNo)
	
	StopRDS = Xlate('REACT_STATE', ReactNo, 'LAST_RDS_NO', 'X')
	
	// Get list of RDS on current reactor that are not FQA
	ReactorConfigKey = 'WO_DAILY_SCHED':ReactNo
	WoNo = Field(Xlate('CONFIG', ReactorConfigKey, WOCust$, 'X'), ' ', 1)
	RDSColumns        = ''
	RDSColumns<0, 1>  = 'SEQ'
	RDSList = RDS_Services('GetRDSData', WoNo, RDSColumns, False$, '')
	
	// Find the index of StopRDS
	RDSList      = SRP_Sort_Array(RDSList, "AN1")
	RDSFastArray = SRP_Fastarray("Create", RDSList)
	StopRDSIdx   = 0
	SRP_Fastarray("Match", RDSFastArray, StopRDS, StopRDSIdx)
	RDSList      = SRP_Fastarray("GetVariable", RDSFastArray)
	
	RdsKeys         = '':@FM:''
	RdsKeysPosition = 1
	RdsIdx          = StopRDSIdx + 1
	DateIn          = ''
	DateOut         = ''
	Loop
		DateIn = Xlate('RDS', RDSList<RdsIdx>, RDS_DATE_IN$, 'X')
		DateOut = Xlate('RDS', RDSList<RdsIdx>, RDS_DATE_OUT$, 'X')
	While (DateIn NE '') and (DateOut EQ '')
		RdsKeys<RdsKeysPosition> = RDSList<RdsIdx>
		If (RdsKeysPosition EQ 1) then
			RdsKeysPosition = 2
		end else
			RdsKeysPosition = 1
		end
		RdsIdx += 1
	Repeat
	
	Response = RdsKeys
	
end service


Service ActiveProveInPreventsModeChange(ReactNo, NewMode, CurrentMode)

	If (ReactNo NE '') then
		If (NewMode NE '') then 
			ReactorRec    = Database_Services('ReadDataRow', 'REACTOR', ReactNo)
			ActiveProveIn = Reactor_Services('GetProveInActive', ReactNo)
			If ActiveProveIn EQ True$ then
				Nica_Orders_Services('GetOrderUpdates')
				ActiveProveIn = Reactor_Services('GetProveInActive', ReactNo)
				If ActiveProveIn EQ True$ then
					Begin Case
						Case CurrentMode EQ 'INITIATE_IDLE'
							IdleStartupActive   = Reactor_Services('GetIdleStartupRequired', ReactNo)
							If IdleStartupActive EQ True$ then
								Response = True$
							end else
								Response = False$
							end
						Case Len(NewMode) GE 2 and NewMode[1, 2] EQ 'UP'
							Response = True$
						Case Otherwise$
							Response = False$
					End Case
				end else
					Response = False$
				end
			end else
				Response = False$
			end
		end else
			Error_Services('Add', 'Error in ' : Service : ' service. NewMode was empty')
		end
	end else
		Error_Services('Add', 'Error in ' : Service : ' service. ReactNo was empty')
	end
	
end service


Service GetReactorDownLL(ReactNo)
	
	Response = ''
	If RowExists('REACTOR', ReactNo) then
		LLDisabledID = Database_Services('ReadDataColumn', 'REACTOR', ReactNo, REACTOR_ACTIVE_LL_DISABLED$, True$, 0)
		If LLDisabledID NE '' then
			If RowExists('REACT_LL', LLDisabledID) then
				DownLoadLock = Database_Services('ReadDataColumn', 'REACT_LL', LLDisabledID, REACT_LL_DISABLED$, True$, 0)
				Response = DownLoadLock
			end
		end
	end
	
end service


Service GetReactorLoadCountLimit(ReactNo)
	
	ErrMsg       = 'Error in ReactorServices -> GetReactorLoadCountLimit: '
	MaxLoadCount = 999
	If ReactNo NE '' then
		If RowExists('REACTOR', ReactNo) then
			ReactorRec = Database_Services('ReadDataRow', 'REACTOR', ReactNo)
			If Error_Services('NoError') then
				ReactType           = ReactorRec<REACTOR_REACT_TYPE$>
				PickPlace           = ReactorRec<REACTOR_PICK_PLACE$>
				DisabledLoadLockCnt = DCount(ReactorRec<REACTOR_ACTIVE_LL_DISABLED$>, @VM)
				Begin Case
					Case ReactType EQ 'ASM'
						If PickPlace then
							MaxLoadCount = 2 - DisabledLoadLockCnt
						end else
							MaxLoadCount = 1
						end
					Case ReactType EQ 'ASM+'
						If PickPlace then
							MaxLoadCount = 2 - DisabledLoadLockCnt
						end else
							MaxLoadCount = 1
						end
					Case ReactType EQ 'HTR'
						If PickPlace then
							MaxLoadCount = 2 - DisabledLoadLockCnt
						end else
							MaxLoadCount = 1
						end
					Case ReactType EQ 'EPP'
						MaxLoadCount = 2;*EPP needs to have an additional load capability.
				End Case
			end else
				ErrMsg := Error_Services('GetMessage')
				Error_Services('Add', ErrMsg)
			end
		end else
			ErrMsg := 'Reactor ' : ReactNo : ' does not exist.'
			Error_Services('Add', ErrMsg)
		end
	end else
		ErrMsg := 'Reactor Number was null.'
		Error_Services('Add', ErrMsg)
	end
	Response = MaxLoadCount
	
end service


Service GetReactorAvailChamberCount(ReactNo)
	StartTick = GetTickCount()
    MetricName = 'GetReactorAvailChamberCount'
	
	ErrMsg                = 'Error in ReactorServices -> GetReactorAvailChamberCount: '
	AvailableChamberCount = 0
	If ReactNo NE '' then
		If RowExists('REACTOR', ReactNo) then
			LoadedRDS          = Reactor_Services('GetReactorCurrLoadRDS', ReactNo)
			ReactorCurrLoadCnt = DCount(LoadedRDS, @VM)
			ReactorCurrCapacity   = Reactor_Services('GetReactorLoadCountLimit', ReactNo)
			AvailableChamberCount = ReactorCurrCapacity - ReactorCurrLoadCnt
		end else
			ErrMsg := 'Reactor ' : ReactNo : ' does not exist.'
			Error_Services('Add', ErrMsg)
		end
	end else
		ErrMsg := 'Reactor Number was null.'
		Error_Services('Add', ErrMsg)
	end
	Response = AvailableChamberCount
	
	EndTick = GetTickCount()
    Mona_Services('QueueLatencyAndCountMetrics', MonaResource, MetricName, StartTick, EndTick)
end service


Service RemoveRDSFromReactorLoad(RDSNo, ReactorNo, User)
	
	Response = 0
	If User NE '' then
			If RDSNo NE '' then
				If ReactorNo NE '' then
					ReactStatusRec = Database_Services('ReadDataRow', 'REACT_STATUS', ReactorNo)
					If Error_Services('NoError') then
							Locate RDSNo in ReactStatusRec<REACT_STATUS_LOAD_RDS$> setting RDSPos then
								ReactStatusRec = Delete(ReactStatusREc, REACT_STATUS_LOAD_RDS$, RDSPos, 0)
								ReactStatusRec = Delete(ReactStatusREc, REACT_STATUS_LOAD_RDS_DTM$, RDSPos, 0)
								ReactStatusRec = Delete(ReactStatusREc, REACT_STATUS_LOAD_CASS_ID$, RDSPos, 0)
								ReactStatusRec = Delete(ReactStatusREc, REACT_STATUS_LOAD_DTM$, RDSPos, 0)
								Database_Services('WriteDataRow', 'REACT_STATUS', ReactorNo, ReactStatusRec, 1, 0, 1)
								If Error_Services('NoError') then
									Response = 1
								end else
									ErrMsg = Error_Services('GetMessage')
									Error_Services('Add', ErrMsg)
								end
							end else
								Error_Services('Add', 'Error in Reactor Services -> RemoveRDSFromReactorLoad: RDS was not found as a loaded lot.')
							end
					end else
						ErrMsg = Error_Services('GetMessage')
						Error_Services('Add', ErrMsg)
					end
				end else
					Error_Services('Add', 'Error in Reactor Services -> RemoveRDSFromReactorLoad: No Reactor No was supplied.')
				end
			end else
				Error_Services('Add', 'Error in Reactor Services -> RemoveRDSFromReactorLoad: No RDS No was supplied.')
			end
	end else
		Error_Services('Add', 'Error in Reactor Services -> RemoveRDSFromReactorLoad: No Username was supplied.')
	end

end service


Service AddRDSToReactorLoad(RDSNo, ReactorNo)
	
	Response = 0
	If RDSNo NE '' then
		If ReactorNo NE '' then
			ReactStatusRec = Database_Services('ReadDataRow', 'REACT_STATUS', ReactorNo)
			If Error_Services('NoError') then
				Locate RDSNo in ReactStatusRec<REACT_STATUS_LOAD_RDS$> using @VM setting iPos else
					WONo                                           = Xlate('RDS', RDSNo, 'WO_NO', 'X')
					CassNo                                         = Xlate('RDS', RDSNo, 'CASS_NO', 'X')
					WOMatKey                                       = WONo : '*' : CassNo
					ThisDTM                                        = SRP_Datetime('Now')
					ReactStatusRec<REACT_STATUS_LOAD_RDS$, -1>     = RDSNo
					ReactStatusRec<REACT_STATUS_LOAD_RDS_DTM$, -1> = ThisDTM
					ReactStatusRec<REACT_STATUS_LOAD_CASS_ID$, -1> = WOMatKey
					ReactStatusRec<REACT_STATUS_LOAD_DTM$, -1>     = ThisDTM
					Database_Services('WriteDataRow', 'REACT_STATUS', ReactorNo, ReactStatusRec, 1, 0, 1)
				end 
			end else
				ErrMsg = Error_Services('GetMessage')
				Error_Services('Add', ErrMsg)
			end
		end else
			Error_Services('Add', 'Error in Reactor Services -> RemoveRDSFromReactorLoad: No Reactor No was supplied.')
		end
	end else
		Error_Services('Add', 'Error in Reactor Services -> RemoveRDSFromReactorLoad: No RDS No was supplied.')
	end
	
end service


Service UpdateReactorIQSViolations()
	
	ReactNos = Reactor_Services('GetReactorNumbers')
	If Error_Services('NoError') then
		CurrViols = Database_Services('ReadDataRow', 'CONFIG', 'IQS_VIOL_DATA')
		If Error_Services('NoError') then
			ViolatingReactors = CurrViols<IQS_VIOL_DATA.REACTOR$>
			ViolationDtms     = CurrViols<IQS_VIOL_DATA.LAST_ALARM$>
			ViolationTests    = CurrViols<IQS_VIOL_DATA.TEST$>
			Swap ' AM' with 'AM' in ViolationDtms
			Swap ' PM' with 'PM' in ViolationDtms
			ViolationDtms = IConv(ViolationDtms, 'DT')
			For each ReactNo in ReactNos
				ActiveAlarm = False$
				For each ViolReactor in ViolatingReactors using @VM setting vPos
					If ViolReactor EQ ReactNo then
						ActiveAlarm   = True$
						ThisAlarmDtm  = ViolationDtms<0, vPos>
						ThisAlarmTest = ViolationTests<0, vPos>
						// Check if a new alarm and update if necessary
						LastAlarmDtm  = Xlate('REACTOR', ReactNo, REACTOR_IQS_ALARM_DTM$, 'X')
						If LastAlarmDtm LT ThisAlarmDtm then
							// New alarm
							HaveLock = Database_Services('GetKeyIDLock', 'REACTOR', ReactNo, True$)
							If HaveLock then
								ReactorRec = Database_Services('ReadDataRow', 'REACTOR', ReactNo)
								If Error_Services('NoError') then 
									ReactorRec<REACTOR_IQS_ALARM_DTM$>  = ThisAlarmDtm
									ReactorRec<REACTOR_IQS_ALARM_TEST$> = ThisAlarmTest
									// WriteDataRow will clear the lock as long as "IgnoreAllLocks" is false
									// REACTOR_ACTIONS will be triggered, which will generate a NICA order if a new HgCV alarm is detected
									Database_Services('WriteDataRow', 'REACTOR', ReactNo, ReactorRec, True$, False$, False$)							
								end
							end
						end						
					end
				Next ViolReactor				
				
				If Not(ActiveAlarm) then 
					// Clear last active alarm
					HaveLock     = Database_Services('GetKeyIDLock', 'REACTOR', ReactNo, True$)
					If HaveLock then
						ReactorRec    = Database_Services('ReadDataRow', 'REACTOR', ReactNo)
						If Error_Services('NoError') then
							CurrAlarmDtm  = ReactorRec<REACTOR_IQS_ALARM_DTM$>
							CurrAlarmTest = ReactorRec<REACTOR_IQS_ALARM_TEST$>
							If ( (CurrAlarmDtm NE '') or (CurrAlarmTest NE '') ) then
								ReactorRec<REACTOR_IQS_ALARM_DTM$>  = ''
								ReactorRec<REACTOR_IQS_ALARM_TEST$> = ''
								// WriteDataRow will clear the lock as long as "IgnoreAllLocks" is false
								Database_Services('WriteDataRow', 'REACTOR', ReactNo, ReactorRec, True$, False$, False$)								
							end	else
								Database_Services('ReleaseKeyIDLock', 'REACTOR', ReactNo)
							end
						end
					end					
				end
			Next ReactNo
		end
	end
	
end service


Service GetProveInActive(ReactNo)

	If ReactNo NE '' then
		OrderTypes       = 'CHANGEOVER' : @VM : 'INITIATE_IDLE' : @VM : 'IDLE'
		ActiveProveInIds = Nica_Orders_Services('GetActiveOrders', 'REACTOR', ReactNo, OrderTypes)
		Response         = (ActiveProveInIds NE '')
	end else
		Error_Services('Add', 'Error in ':Service:' service. Null ReactNo passed into service.')
	end
	
end service


Service GetActiveProveInTypes(ReactNo)
	
	If ReactNo NE '' then 
		ActiveProveInOrderIds = Reactor_Services('GetActiveProveInOrderIds', ReactNo)
		If ActiveProveInOrderIds NE '' then
			Response = Xlate('NICA_ORDERS', ActiveProveInOrderIds, 'ORDER_TYPE', 'X')
		end
	end else
		Error_Services('Add', 'Error in ':Service:' service. Null ReactNo passed into service.')
	end
	
end service


Service GetActiveProveInProgresses(ReactNo)
	
	Response = ''
	If ReactNo NE '' then 
		ActiveProveInOrderIds = Reactor_Services('GetActiveProveInOrderIds', ReactNo)
		If ActiveProveInOrderIds NE '' then
			OrderTypes = Xlate('NICA_ORDERS', ActiveProveInOrderIds, 'ORDER_TYPE', 'X')
			OrderCount = Dcount(OrderTypes, @VM)
			For I = 1 to OrderCount
			    OrderType = OrderTypes<I>
			    ProgressPercentage = Xlate('NICA_ORDERS', ActiveProveInOrderIds<i>, 'PROGRESS_PERCENTAGE', 'X')
			    Response<-1> = OrderType :  '- ' : ProgressPercentage :'% Complete.'
			Next I
		end
	end else
		Error_Services('Add', 'Error in ':Service:' service. Null ReactNo passed into service.')
	end
	
end service


Service GetActiveProveInOrderIds(ReactNo)
	
	If ReactNo NE '' then
		OrderTypes          = 'CHANGEOVER' : @VM : 'INITIATE_IDLE' : @VM : 'IDLE'
		Response            = Nica_Orders_Services('GetActiveOrders', 'REACTOR', ReactNo, OrderTypes)		
	end else
		Error_Services('Add', 'Error in ':Service:' service. Null ReactNo passed into service.')
	end
	
end service


Service GetIdleStartupRequired(ReactNo)
	
	If ReactNo NE '' then 
		Response        = Xlate('REACTOR', ReactNo, REACTOR_IDLE_STARTUP_REQUIRED$, 'X')
	end else
		Error_Services('Add', 'Error in ':Service:' service. Null ReactNo passed into service.')
	end
	
end service


Service GetCurrentInjectorSettings(ReactorNo)
    
    ErrorMsg        = ''
    CurrInjSettings = ''
    If ReactorNo NE '' then
        If RowExists('REACTOR', ReactorNo) then
            Open 'DICT.REACTOR_INJECTOR_SETTINGS' to hDict then
                Query    = 'REACTOR_NO':@VM:ReactorNo:@FM
                Query   := 'ACTIVE':@VM:True$:@FM
                KeyList  = ''
                Option   = 'E'
                Flag     = ''
                Btree.Extract(Query, 'REACTOR_INJECTOR_SETTINGS', hDict, KeyList, Option, Flag)
                If Flag EQ 0 then
                    Begin Case
                        Case DCount(KeyList, @VM) EQ 0
                            CurrInjSettings<0, 1> = ''
                            CurrInjSettings<0, 2> = ''
                            CurrInjSettings<0, 3> = ''
                            CurrInjSettings<0, 4> = ''
                            CurrInjSettings<0, 5> = ''                
                        Case DCount(KeyList, @VM) EQ 1
                            SettingsRec = Database_Services('ReadDataRow', 'REACTOR_INJECTOR_SETTINGS', KeyList)
                            If Error_Services('NoError') then
                                CurrInjSettings<0, 1> = SettingsRec<REACTOR_INJECTOR_SETTINGS.INJECTOR_1$>
                                CurrInjSettings<0, 2> = SettingsRec<REACTOR_INJECTOR_SETTINGS.INJECTOR_2$>
                                CurrInjSettings<0, 3> = SettingsRec<REACTOR_INJECTOR_SETTINGS.INJECTOR_3$>
                                CurrInjSettings<0, 4> = SettingsRec<REACTOR_INJECTOR_SETTINGS.INJECTOR_4$>
                                CurrInjSettings<0, 5> = SettingsRec<REACTOR_INJECTOR_SETTINGS.INJECTOR_5$>
                            end                        
                        Case DCount(KeyList, @VM) GT 1
                            ErrorMsg = 'Error in ':Service:' service. Multiple active REACTOR_INJECTOR_SETTINGS records returned.'
                    End Case
                end else
                    ErrorMsg = 'Error in ':Service:' service. Error calling Btree.Extract.'
                end
            end else
                ErrorMsg = 'Error in ':Service:' service. Error opening DICT.REACTOR_INJECTOR_SETTINGS.'
            end
        end else
            ErrorMsg = 'Error in ':Service:' service. REACTOR, ':ReactorNo:', does not exist.'
        end
    end else
        ErrorMsg = 'Error in ':Service:' service. Null ReactorNo passed into service.'
    end
    
    If ErrorMsg EQ '' then
        Response = CurrInjSettings
    end else
        Error_Services('Add', ErrorMsg)
    end
    
end service


Service GetCurrentRatios(ReactorNo)
    
    ErrorMsg   = ''
    CurrRatios = ''
    If ReactorNo NE '' then
        If RowExists('REACTOR', ReactorNo) then 
            Open 'DICT.REACTOR_RATIOS' to hDict then
                Query    = 'REACTOR_NO':@VM:ReactorNo:@FM
                Query   := 'ACTIVE':@VM:True$:@FM
                KeyList  = ''
                Option   = 'E'
                Flag     = ''
                Btree.Extract(Query, 'REACTOR_RATIOS', hDict, KeyList, Option, Flag)
                If Flag EQ 0 then
                    NumRatios   = ''
                    ReactorType = Xlate('REACTOR', ReactorNo, REACTOR_REACT_TYPE$, 'X')
                    Begin Case
                        Case ReactorNo EQ 73
                            // OpenInsight needs to be updated to support reactor type HTR+, which requires 16 ratios.
                            NumRatios = 16
                        Case ReactorType EQ 'ASM' or ReactorType EQ 'ASM+'
                            NumRatios = 10
                        Case ReactorType EQ 'HTR'
                            NumRatios = 12
                        Case Otherwise$
                            ErrorMsg  = 'Error in ':Service:' service. Error determining number '
                            ErrorMsg := 'of Ratios required for reactor ':ReactorNo:'.'
                    End Case
                    If ErrorMsg EQ '' then                     
                        Begin Case
                            Case DCount(KeyList, @VM) EQ 0
                                For RatioIndex = 1 to NumRatios
                                    CurrRatios<0, RatioIndex> = ''
                                Next RatioIndex
                            Case DCount(KeyList, @VM) EQ 1
                                SettingsRec = Database_Services('ReadDataRow', 'REACTOR_RATIOS', KeyList)
                                If Error_Services('NoError') then
                                    For RatioIndex = 0 to (NumRatios - 1)
                                        CurrRatios<0, RatioIndex + 1> = SettingsRec<REACTOR_RATIOS.RATIO_1$ + RatioIndex>
                                    Next RatioIndex
                                end                            
                            Case DCount(KeyList, @VM) GT 1
                                ErrorMsg = 'Error in ':Service:' service. Multiple active REACTOR_RATIOS records returned.'
                        End Case
                    end
                end else
                    ErrorMsg = 'Error in ':Service:' service. Error calling Btree.Extract.'
                end
            end else
                ErrorMsg = 'Error in ':Service:' service. Error opening DICT.REACTOR_RATIOS.'
            end
        end else
            ErrorMsg = 'Error in ':Service:' service. REACTOR, ':ReactorNo:', does not exist.'
        end
    end else
        ErrorMsg = 'Error in ':Service:' service. Null ReactorNo passed into service.'
    end
    
    If ErrorMsg EQ '' then
        Response = CurrRatios
    end else
        Error_Services('Add', ErrorMsg)
    end    
    
end service


Service GetRatiosByReactorLog(ReactorLogId)
    
    RatiosKey = ''
    Ratios    = ''
    ErrorMsg  = ''
    ReactorNo = ''
    If ReactorLogId NE '' then
        If RowExists('REACTOR_LOG', ReactorLogId) then
            Query    = 'SELECT REACTOR_RATIOS WITH REACTOR_LOG_ID EQ ':Quote(ReactorLogId)
            Query   := ' BY-DSND RATIO_SET_DTM'
            RList(Query, TARGET_ACTIVELIST$)
            ErrCode  = ''
            If Not(Get_Status(ErrCode)) then
                If @RecCount GT 0 then
                    ReadNext RatiosKey else
                        ErrorMsg  = 'Error in ':Service:' service. Error looking up reactor '
                        ErrorMsg := 'ratios associated with REACTOR_LOG ':ReactorLogId:'.'
                    end
                    ClearSelect TARGET_ACTIVELIST$                   
                end
            end else
                ErrorMsg  = 'Error in ':Service:' service. Error looking up reactor '
                ErrorMsg := 'ratios associated with REACTOR_LOG ':ReactorLogId:'.'
            end
        end else
            ErrorMsg = 'Error in ':Service:' service. REACTOR_LOG ':ReactorLogId:' does not exist.'
        end
    end else
        ErrorMsg = 'Error in ':Service:' service. Null ReactorLogId passed into service.'
    end
    
    If ErrorMsg EQ '' then
        If RatiosKey NE '' then
            RatiosRec = Database_Services('ReadDataRow', 'REACTOR_RATIOS', RatiosKey)
            If Error_Services('NoError') then
                If ReactorNo EQ '' then ReactorNo = Xlate('REACTOR_LOG', ReactorLogId, 'REACTOR', 'X')
                If ReactorNo NE '' then 
                    NumRatios = Reactor_Services('GetNumRatios', ReactorNo)
                    For RatioIndex = 0 to (NumRatios - 1)
                        Ratios<0, RatioIndex + 1> = RatiosRec<REACTOR_RATIOS.RATIO_1$ + RatioIndex>
                    Next RatioIndex
                end else
                    ErrorMsg = 'Error in ':Service:' service. Null Reactor in REACTOR_LOG ':ReactorLogId:'.'
                end
            end else
                ErrorMsg = 'Error in ':Service:' service. Error reading REACTOR_RATIOS record ':RatiosKey:'.'
            end
        end
    end
    
    If ErrorMsg EQ '' then
        Response = Ratios
    end else
        Error_Services('Add', ErrorMsg)
    end    
    
end service


Service GetInjectorSettingsByReactorLog(ReactorLogId)
    
    ReactInjSetKey = ''
    InjSettings    = ''
    ErrorMsg       = ''
    If ReactorLogId NE '' then
        If RowExists('REACTOR_LOG', ReactorLogId) then
            Query    = 'SELECT REACTOR_INJECTOR_SETTINGS WITH REACTOR_LOG_ID EQ ':Quote(ReactorLogId)
            Query   := ' BY-DSND INJ_SET_DTM'
            RList(Query, TARGET_ACTIVELIST$)
            ErrCode  = ''
            If Not(Get_Status(ErrCode)) then
                If @RecCount GT 0 then
                    ReadNext ReactInjSetKey else
                        ErrorMsg  = 'Error in ':Service:' service. Error looking up reactor injector '
                        ErrorMsg := 'settings associated with REACTOR_LOG ':ReactorLogId:'.'
                    end
                    ClearSelect TARGET_ACTIVELIST$                   
                end
            end else
                ErrorMsg  = 'Error in ':Service:' service. Error looking up reactor injector '
                ErrorMsg := 'settings associated with REACTOR_LOG ':ReactorLogId:'.'
            end
        end else
            ErrorMsg = 'Error in ':Service:' service. REACTOR_LOG ':ReactorLogId:' does not exist.'
        end
    end else
        ErrorMsg = 'Error in ':Service:' service. Null ReactorLogId passed into service.'
    end
    
    If ErrorMsg EQ '' then
        If ReactInjSetKey NE '' then
            ReactInjSetRec = Database_Services('ReadDataRow', 'REACTOR_INJECTOR_SETTINGS', ReactInjSetKey)
            If Error_Services('NoError') then
                For InjIndex = 0 to 4
                    InjSettings<0, InjIndex + 1> = ReactInjSetRec<REACTOR_INJECTOR_SETTINGS.INJECTOR_1$ + InjIndex>
                Next InjIndex
            end else
                ErrorMsg = 'Error in ':Service:' service. Error reading REACTOR_INJECTOR_SETTINGS record ':ReactInjSetKey:'.'
            end
        end
    end
    
    If ErrorMsg EQ '' then
        Response = InjSettings
    end else
        Error_Services('Add', ErrorMsg)
    end
    
end service


Service SetNewInjectorSettings(ReactorNo, InjSettings)
    
    // Injector settings should be passed in internal format (MD3)
    ErrorMsg = ''
    If ReactorNo NE '' then
        If RowExists('REACTOR', ReactorNo) then
            If InjSettings NE '' then
                If DCount(InjSettings, @VM) EQ 5 then
                    For each InjSetting in InjSettings using @VM setting vPos
                        If InjSettings EQ '' then ErrorMsg = 'Error in ':Service:' service. Injector setting ':vPos:' is missing.'
                    Until ErrorMsg NE ''
                    Next InjSetting
                    If ErrorMsg EQ '' then
                        CurrInjSettings = Reactor_Services('GetCurrentInjectorSettings', ReactorNo)
                        If (CurrInjSettings NE InjSettings) then 
                            CurrRlId = Xlate('CONFIG', 'REACT_MODE':ReactorNo, 8, 'X')
                            If CurrRlId NE '' then
                                Query = 'REACTOR_NO':@VM:ReactorNo:@FM:'ACTIVE':@VM:True$:@FM
                                Open 'DICT.REACTOR_INJECTOR_SETTINGS' to hDict then
                                    Option                = 'E'
                                    ActiveInjSettingsKeys = ''
                                    Flag                  = ''
                                    Btree.Extract(Query, 'REACTOR_INJECTOR_SETTINGS', hDict, ActiveInjSettingsKeys, Option, Flag)
                                    If Flag EQ 0 then
                                        If ActiveInjSettingsKeys NE '' then
                                            Open 'REACTOR_INJECTOR_SETTINGS' to hTable then
                                                For each ActiveInjSettingsKey in ActiveInjSettingsKeys using @VM
                                                    WriteV False$ on hTable, ActiveInjSettingsKey, REACTOR_INJECTOR_SETTINGS.ACTIVE$ else
                                                        ErrorMsg  = 'Error in ':Service:' service. Error setting previous injector '
                                                        ErrorMsg := 'settings to inactive. File error: ':@File_Error
                                                    end
                                                Until ErrorMsg NE ''
                                                Next ActiveInjSettingsKey
                                            end else
                                                ErrorMsg  = 'Error in ':Service:' service. Error opening REACTOR_INJECTOR_SETTINGS to '
                                                ErrorMsg := 'invalidate previous settings.'
                                            end
                                        end
                                        If ErrorMsg EQ '' then
                                            NewInjSettingsKey = RTI_CreateGUID()
                                            If NewInjSettingsKey NE '' then
                                                NewInjSettingsRec                                            = ''
                                                NewInjSettingsRec<REACTOR_INJECTOR_SETTINGS.REACTOR_LOG_ID$> = CurrRlId
                                                NewInjSettingsRec<REACTOR_INJECTOR_SETTINGS.REACTOR_NO$>     = ReactorNo
                                                NewInjSettingsRec<REACTOR_INJECTOR_SETTINGS.INJ_SET_DTM$>    = Datetime()
                                                NewInjSettingsRec<REACTOR_INJECTOR_SETTINGS.ACTIVE$>         = True$
                                                For InjIndex = 0 to 4
                                                    NewInjSettingsRec<REACTOR_INJECTOR_SETTINGS.INJECTOR_1$ + InjIndex> = InjSettings<0, InjIndex + 1>
                                                Next InjIndex
                                                Database_Services('WriteDataRow', 'REACTOR_INJECTOR_SETTINGS', NewInjSettingsKey, NewInjSettingsRec)
                                                If Error_Services('NoError') then
                                                    plParms  = 'REACT_STATE':@RM
                                                    plParms := ReactorNo:@RM
                                                    plParms := REACT_STATE_CURR_INJ_RL_ID$:@RM
                                                    plParms := CurrRlId:@RM
                                                    obj_Post_Log('Create',plParms)
                                                    If Get_Status(ErrCode) then
                                                        ErrorMsg  = 'Error in ':Service:' service. Error calling obj_Post_Log("Create"). '
                                                        ErrorMsg := 'Error code: ':ErrCode:'.'
                                                    end                                                    
                                                end else
                                                    ErrorMsg = Error_Services('GetMessage')
                                                end
                                            end else
                                                ErrorMsg = 'Error in ':Service:' service. Error creating new REACTOR_INJECTOR_SETTINGS record.'
                                            end                                                     
                                        end
                                    end else
                                        ErrorMsg = 'Error in ':Service:' service. Error calling Btree.Extract.'
                                    end
                                end else
                                    ErrorMsg = 'Error in ':Service:' service. Error opening DICT.REACTOR_INJECTOR_SETTINGS'
                                end
                            end else
                                ErrorMsg = 'Error in ':Service:' service. Error reading current REACTOR_LOG Id.'
                            end
                        end else
                            ErrorMsg = 'Error in ':Service:' service. Input InjSettings are the same as the current InjSettings.'
                        end
                    end
                end else
                    ErrorMsg = 'Error in ':Service:' service. All five injector settings are required.'
                end
            end else
                ErrorMsg = 'Error in ':Service:' service. Null InjSettings passed into service.'
            end
        end else
            ErrorMsg = 'Error in ':Service:' service. REACTOR, ':ReactorNo:', does not exist.'
        end
    end else
        ErrorMsg = 'Error in ':Service:' service. Null ReactorNo passed into service.'
    end
    
    If ErrorMsg EQ '' then
        Response = True$
    end else
        Error_Services('Add', ErrorMsg)
        Response = False$
    end        
    
end service


Service SetNewRatios(ReactorNo, Ratios)
    
    // Ratios should be passed in internal format (MD3)
    ErrorMsg = ''
    If ReactorNo NE '' then
        If RowExists('REACTOR', ReactorNo) then
            If Ratios NE '' then
                Ratios    = SRP_Array('Clean', Ratios, 'Trim', @VM)
                NumRatios = Reactor_Services('GetNumRatios', ReactorNo)
                If Error_Services('NoError') then 
                    If DCount(Ratios, @VM) EQ NumRatios then
                        For each Ratio in Ratios using @VM setting vPos
                            Begin Case
                                Case Ratio EQ ''
                                    ErrorMsg = 'Error in ':Service:' service. Ratio ':vPos:' is missing.'
                                Case OConv(Ratio, 'MD3') LT 30
                                    ErrorMsg = 'Error in ':Service:' service. Invalid Ratio value. Ratio ':vPos:' is less than 30.'
                                Case OConv(Ratio, 'MD3') GT 100
                                    ErrorMsg = 'Error in ':Service:' service. Invalid Ratio value. Ratio ':vPos:' is greater than 100.'
                            End Case
                        Until ErrorMsg NE ''
                        Next Ratio
                        If ErrorMsg EQ '' then
                            CurrRatios = Reactor_Services('GetCurrentRatios', ReactorNo)
                            If (CurrRatios NE Ratios) then 
                                CurrRlId = Xlate('CONFIG', 'REACT_MODE':ReactorNo, 8, 'X')
                                If CurrRlId NE '' then
                                    Query = 'REACTOR_NO':@VM:ReactorNo:@FM:'ACTIVE':@VM:True$:@FM
                                    Open 'DICT.REACTOR_RATIOS' to hDict then
                                        Option           = 'E'
                                        ActiveRatiosKeys = ''
                                        Flag             = ''
                                        Btree.Extract(Query, 'REACTOR_RATIOS', hDict, ActiveRatiosKeys, Option, Flag)
                                        If Flag EQ 0 then
                                            If ActiveRatiosKeys NE '' then
                                                Open 'REACTOR_RATIOS' to hTable then
                                                    For each ActiveRatiosKey in ActiveRatiosKeys using @VM
                                                        WriteV False$ on hTable, ActiveRatiosKey, REACTOR_RATIOS.ACTIVE$ else
                                                            ErrorMsg  = 'Error in ':Service:' service. Error setting previous ratios '
                                                            ErrorMsg := 'record to inactive. File error: ':@File_Error
                                                        end
                                                    Until ErrorMsg NE ''
                                                    Next ActiveRatiosKey
                                                end else
                                                    ErrorMsg  = 'Error in ':Service:' service. Error opening REACTOR_RATIOS table to '
                                                    ErrorMsg := 'invalidate previous ratios.'
                                                end
                                            end
                                            If ErrorMsg EQ '' then
                                                NewRatiosKey = RTI_CreateGUID()
                                                If NewRatiosKey NE '' then
                                                    NewRatiosRec                                 = ''
                                                    NewRatiosRec<REACTOR_RATIOS.REACTOR_LOG_ID$> = CurrRlId
                                                    NewRatiosRec<REACTOR_RATIOS.REACTOR_NO$>     = ReactorNo
                                                    NewRatiosRec<REACTOR_RATIOS.RATIO_SET_DTM$>  = Datetime()
                                                    NewRatiosRec<REACTOR_RATIOS.ACTIVE$>         = True$
                                                    For RatioIndex = 0 to (NumRatios - 1)
                                                        NewRatiosRec<REACTOR_RATIOS.RATIO_1$ + RatioIndex> = Ratios<0, RatioIndex + 1>
                                                    Next RatioIndex
                                                    Database_Services('WriteDataRow', 'REACTOR_RATIOS', NewRatiosKey, NewRatiosRec)
                                                    If Error_Services('NoError') then
                                                        plParms  = 'REACT_STATE':@RM
                                                        plParms := ReactorNo:@RM
                                                        plParms := REACT_STATE_CURR_RATIO_RL_ID$:@RM
                                                        plParms := CurrRlId:@RM
                                                        obj_Post_Log('Create',plParms)
                                                        If Get_Status(ErrCode) then
                                                            ErrorMsg = 'Error in ':Service:' service. Error calling obj_Post_Log("Create"). '
                                                            ErrorMsg = 'Error code: ':ErrCode:'.'
                                                        end                                                    
                                                    end else
                                                        ErrorMsg = Error_Services('GetMessage')
                                                    end
                                                end else
                                                    ErrorMsg = 'Error in ':Service:' service. Error creating new REACTOR_RATIOS record.'
                                                end                                                     
                                            end
                                        end else
                                            ErrorMsg = 'Error in ':Service:' service. Error calling Btree.Extract.'
                                        end
                                    end else
                                        ErrorMsg = 'Error in ':Service:' service. Error opening DICT.REACTOR_RATIOS'
                                    end
                                end else
                                    ErrorMsg = 'Error in ':Service:' service. Error reading current REACTOR_LOG Id.'
                                end
                            end else
                                ErrorMsg = 'Error in ':Service:' service. Input Ratios are the same as the current Ratios.'
                            end
                        end
                    end else
                        ErrorMsg  = 'Error in ':Service:' service. Reactor ':ReactorNo:' requires ':NumRatios:' values '
                        ErrorMsg := 'for ratios. Only ':DCount(Ratios, @VM):' were supplied.'
                    end
                end else
                    ErrorMsg  = 'Error in ':Service:' service. Error determining number of required ratios '
                    ErrorMsg := 'for reactor ':ReactorNo:'. Error message: ':Error_Services("GetMessage")
                end
            end else
                ErrorMsg = 'Error in ':Service:' service. Null Ratios passed into service.'
            end
        end else
            ErrorMsg = 'Error in ':Service:' service. REACTOR, ':ReactorNo:', does not exist.'
        end
    end else
        ErrorMsg = 'Error in ':Service:' service. Null ReactorNo passed into service.'
    end
    
    If ErrorMsg EQ '' then
        Response = True$
    end else
        Error_Services('Add', ErrorMsg)
        Response = False$
    end     
    
end service


Service GetNumRatios(ReactorNo)
    
    ErrorMsg  = ''
    NumRatios = ''
    If (ReactorNo NE '') then
        If RowExists('REACTOR', ReactorNo) then
            ReactorType = Xlate('REACTOR', ReactorNo, REACTOR_REACT_TYPE$, 'X')
            Begin Case
                Case ReactorNo EQ 73
                    // OpenInsight needs to be updated to support reactor type HTR+, which requires 16 ratios.
                    NumRatios = 16
                Case ReactorType EQ 'ASM' or ReactorType EQ 'ASM+'
                    NumRatios = 10
                Case ReactorType EQ 'HTR'
                    NumRatios = 12
                Case Otherwise$
                    ErrorMsg  = 'Error in ':Service:' service. Error determining number '
                    ErrorMsg := 'of Ratios required for reactor ':ReactorNo:'.'
            End Case            
        end else
            ErrorMsg = 'Error in ':Service:' service. REACTOR, ':ReactorNo:', does not exist.'
        end
    end else
        ErrorMsg = 'Error in ':Service:' service. Null ReactorNo passed into service.'
    end
    
    If ErrorMsg EQ '' then
        Response = NumRatios
    end else
        Error_Services('Add', ErrorMsg)
    end
    
end service

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

RollbackReactUtil:
	
	// Reopen previous REACT_UTIL record
	If OldReactUtilID NE '' then
		OldReactUtilRec                               = Database_Services('ReadDataRow', 'REACT_UTIL', OldReactUtilID)
		OldReactUtilRec<react_util_end_date$>         = ''
		OldReactUtilRec<react_util_end_time$>         = ''
		OldReactUtilRec<react_util_mode_finish_user$> = ''
		Database_Services('WriteDataRow', 'REACT_UTIL', OldReactUtilID, OldReactUtilRec)
	end
	
return


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

