Compile function Report_Services(@Service, @Params)
/***********************************************************************************************************************

    Name        :   Report_Services

    Description :   Handler program for all Report services.

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

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

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

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

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

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

    Metadata    :

    History     :   (Date, Initials, Notes)
        10/30/20    djs     Original programmer.
														

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

$Insert SERVICE_SETUP
$Insert LOGICAL
$Insert OIPRINT_EQUATES
$Insert SCHED_DET_EQUATES
$Insert WO_LOG_EQUATES
$Insert APPCOLORS
$Insert MSG_EQUATES
$Insert SCHEDULE_EQU
$Insert LOCATION_EQUATES
$Insert RLIST_EQUATES
$Insert DICT_EQUATES
$Insert DAILY_METRICS_EQUATES
$Insert RDS_EQUATES
$Insert DAILY_PERFORMANCE_EQUATES
$Insert DAILY_PERFORMANCE_REPORT_EQUATES
$Insert SCHED_DET_NG_EQUATES
$Insert MAKEUP_WAFERS_EQUATES
$Insert REACTOR_EQUATES
$Insert WAFER_COUNTER_EQUATES
$Insert REACTOR_DAILY_UPTIME_EQUATES
$Insert MATERIAL_REPORT_EQUATES

// Reduce modes (for Select statement)
Equ NEW_EXIST$ To 0 
Equ NEXT_CUR$  To 1
Equ ADD_EXIST$ To 2

Declare subroutine Utility, ErrMsg, Set_Status, Set_Printer, RList, SRP_Stopwatch, Btree.Extract, Error_Services, V119
Declare subroutine Push.Select, Pop.Select, Database_Services, SRP_JSON, Logging_Services, Reduce, FSMsg
Declare subroutine Report_Services, Make.List, Update_Index
Declare function   Set_Printer, Get_Printer, Msg, Get_Status, Printer_Select, obj_Install, Dialog_Box, obj_Location
Declare function   Schedule_Services, Error_Services, Location_Services, SRP_Array, Signature_Services, obj_RDS_Test
Declare function   Epi_Part_Services, SRP_Math, Database_Services, SRP_JSON, Reactor_Services, SRP_Datetime
Declare function   SRP_Array, Environment_Services, Logging_Services, Datetime, Select_Into, Wafer_Counter_Services
Declare function    Lot_Services, Test_Run_Services, NCR_Services, Date_Services

Equ TAB   TO \09\

LogPath     = Environment_Services('GetApplicationRootPath') : '\LogFiles\RDS'
LogDate     = Oconv(Date(), 'D4/')
LogTime     = Oconv(Time(), 'MTS')
LogFileName  = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' WAFERS_IN.csv'
Headers      = 'Logging DTM' : @FM : 'RDS Key ID' : @FM : 'WAFERS_IN'
objWafersLog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, ',', Headers, '', False$, False$)

LogPath       = Environment_Services('GetApplicationRootPath') : '\LogFiles\Report_Services'
LogFileName   = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' CalcDailyPerformanceData.csv'
Headers       = 'Logging DTM' : @FM : 'Service Step' : @FM : 'Service Notes'
objReportsLog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, ',', Headers, '', False$, False$)

LoggingDTM  = LogDate : ' ' : LogTime   ; // Logging DTM

GoToService

Return Response or ""

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Service Parameter Options
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Options BOOLEAN     = True$, False$

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

Service GetMakeupInventoryReportJSON()
	
	clearselect
	Statement1 = 'LIST MAKEUP_WAFERS WO_NO CASS_NO CUST_NO CUST_PART_NO EPI_PART_NO PROD_ORD_NO PROD_VER_NO PS_NO RDS_NO REACT_TYPE SAP_BATCH_NO UNLOAD_DTM WAFER_SIZE WM_OUT_NO WFR_QTY EXPIRED BY WAFER_SIZE BY PS_NO'
	MUInv = Select_Into(Statement1, 'EDT')
	
	If Error_Services('NoError') then
		MUInvJSON = ''
		objJSON   = ''
		If SRP_JSON(objJSON, 'New', 'Object') then
			objReactArray = ''
			If SRP_JSON(objReactArray, 'New', 'Array') then
				For each Row in MUInv using @FM setting fPos
					objRow = ''
					If SRP_JSON(objRow, 'New', 'Object') then
						SRP_JSON(objRow, 'SetValue', 'workOrderNo', Row<0, 2>, 'number')
						SRP_JSON(objRow, 'SetValue', 'cassNo', Row<0, 3>, 'number')
						SRP_JSON(objRow, 'SetValue', 'custNo', Row<0, 4>, 'number')
						SRP_JSON(objRow, 'SetValue', 'custPartNo', Row<0, 5>)
						SRP_JSON(objRow, 'SetValue', 'epiPartNo', Row<0, 6>)
						SRP_JSON(objRow, 'SetValue', 'prodOrderNo', Row<0, 7>)
						SRP_JSON(objRow, 'SetValue', 'prodVerNo', Row<0, 8>, 'number')
						SRP_JSON(objRow, 'SetValue', 'prodSpecNo', Row<0, 9>, 'number')
						SRP_JSON(objRow, 'SetValue', 'rdsNo', Row<0, 10>, 'number')
						SRP_JSON(objRow, 'SetValue', 'reactType', Row<0, 11>)
						SRP_JSON(objRow, 'SetValue', 'sapBatchNo', Row<0, 12>)
						SRP_JSON(objRow, 'SetValue', 'unloadDtm', Row<0, 13>)
						SRP_JSON(objRow, 'SetValue', 'wfrSize', Row<0, 14>)
						SRP_JSON(objRow, 'SetValue', 'wmOutNo', Row<0, 15>)
						SRP_JSON(objRow, 'SetValue', 'wfrQty', Row<0, 16>, 'number')
						
						ReactType = Xlate('WO_MAT', Row<0, 2>:'*':Row<0, 3>, 'REACTOR_TYPE', 'X')
						If ReactType NE 'EPP' then
							LotID = Row<0, 10>
						end else
							LotID = Row<0, 2>:'*1*':Row<0, 3>
						end
						WcRec = Wafer_Counter_Services('GetLastScan', LotID)
						WcQty = WcRec<WAFER_COUNTER.SCAN_QTY$>
						WcDtm = WcRec<WAFER_COUNTER.SCAN_DTM$>
						
						SRP_JSON(objRow, 'SetValue', 'muWaferCounterQty', WcQty)
						SRP_JSON(objRow, 'SetValue', 'muWaferCounterDtm', WcDtm)
						
						SRP_JSON(objRow, 'SetValue', 'expired', IConv(Row<0, 17>, 'B'), 'Boolean')
						
						SRP_JSON(objReactArray, 'Add', objRow)
						SRP_JSON(objRow, 'Release')
					end
				Next Row
				SRP_JSON(objJSON, 'Set', 'MUInvReport', objReactArray)
				SRP_JSON(objReactArray, 'Release')
			end
			Response = SRP_JSON(objJSON, 'Stringify', 'Styled')
			SRP_JSON(objJSON, 'Release')
		end
		
	end
	
end service


Service GenerateEPPMetReport(DateFrom, DateTo, EpiPartNo, ExportDir)
	
	ReportData = ''
	ErrorMsg   = ''
	If ( (DateFrom NE '') and (EpiPartNo NE '') and (ExportDir NE '') ) then
		If DateTo EQ '' then
			DateTo = Date()
		end else
			If Not(Num(DateTo)) then DateTo = IConv(DateTo, 'D')
		end
		If Not(Num(DateFrom)) then DateFrom = IConv(DateFrom, 'D')
		// Select WM_OUT keys
		WMOList = ''
		Query   = 'SELECT WM_OUT WITH PART_NO EQ ':Quote(EpiPartNo)
		GoSub ClearCursors
		Set_Status(0)
		RList(Query, TARGET_ACTIVELIST$, '', '', '')
		If Get_Status(ErrCode) then
			ErrorMsg = 'Error in ':Service:' service. RList failed! Error code: ':ErrCode:'.'
		end
		If ErrorMsg EQ '' then
			Def              = ""
			Def<MCAPTION$>   = "Selecting WM_OUT records..."
			Def<MTYPE$>      = "GC"
			Def<MEXTENT$>    = @RecCount
			Def<MTEXTWIDTH$> = 600
			MsgUp            = Msg(@WINDOW, Def) ;* Start gas guage message
			Counter          = 0
			EOF              = False$
			Loop
				Counter += 1
				Running     = Msg(@WINDOW, MsgUp, Counter, MSGINSTUPDATE$) ;* Update message
				ReadNext WMOKey else EOF = True$
			Until EOF
				WMOList<-1> = WMOKey
			Repeat
			Msg(@WINDOW,MsgUp)
			
			// Filter on RDS {DATE_OUT}
			RDSList       = ''
			FilterWMOList = ''
			NumWMO        = DCount(WMOList, @FM)
			
			Def              = ""
			Def<MCAPTION$>   = "Filtering on dates..."
			Def<MTYPE$>      = "GC"
			Def<MEXTENT$>    = NumWMO
			Def<MTEXTWIDTH$> = 600
			MsgUp            = Msg(@WINDOW, Def) ;* Start gas guage message
			Counter          = 0
			
			For WMOIndex = 1 to NumWMO
				Running     = Msg(@WINDOW, MsgUp, WMOIndex, MSGINSTUPDATE$) ;* Update message
				Done        = False$
				WMOKey      = WMOList<WMOIndex>
				ThisRDSList = Xlate('WM_OUT', WMOKey, 'RDS', 'X')
				ThisRDSList = SRP_Array('Clean', ThisRDSList, 'TrimAndMakeUnique', @VM)
				For each RDSNo in ThisRDSList using @VM
					DateOut = Xlate('RDS', RDSNo, 'DATE_OUT', 'X')
					If ( (DateOut GE DateFrom) and (DateOut LE DateTo) ) then
						FilterWMOList<-1> = WMOKey
						Done              = True$
					end
				Until Done
				Next RDSNo
			Next WMOIndex
			Msg(@WINDOW,MsgUp)
			
			// Build the report
			ReportData    = ''
			Headers       = Database_Services('ReadDataRow', 'SYSLISTS', 'EPP_METROLOGY_HEADERS')
			Columns       = Xlate('EXPORTS', 'EPIPRO_METOROLGY', 'COLUMNS', 'X')
			Swap @FM with @VM in Headers
			ReportData<1> = Headers
			
			RDSFields  = Database_Services('ReadDataRow', 'DICT.RDS', '%FIELDS%')
			RDSColumns = RDSFields<DICT_DISPLAY$>
			RDSConvs   = RDSFields<DICT_GENERIC_TYPE$>
			NumWMO     = DCount(FilterWMOList, @FM)
			
			Def              = ""
			Def<MCAPTION$>   = "Building report..."
			Def<MTYPE$>      = "GC"
			Def<MEXTENT$>    = NumWMO
			Def<MTEXTWIDTH$> = 600
			MsgUp            = Msg(@WINDOW, Def) ;* Start gas guage message
			
			If NumWMO GT 0 then
				For each WMOKey in FilterWMOList using @FM setting fPos
					Running     = Msg(@WINDOW, MsgUp, fPos, MSGINSTUPDATE$) ;* Update message
					RowIndex    = fPos + 1
					ThisRDSList = Xlate('WM_OUT', WMOKey, 'RDS', 'X')
					Zones       = Xlate('WM_OUT', WMOKey, 'ZONE', 'X')
					Pockets     = Xlate('WM_OUT', WMOKey, 'POCKET', 'X')
					SAPBatchNo  = Xlate('WM_OUT', WMOKey, 'SAP_BATCH_NO', 'X')
					InCassNos   = Xlate('WM_OUT', WMOKey, 'IN_CASS_NO', 'X')
					WONo        = Field(WMOKey, '*', 1)
					PrevRDSNo   = ''
					For each RDSNo in ThisRDSList using @VM setting SlotNo
						If RDSNo NE '' then
							If RDSNo NE PrevRDSNo then Database_Services('ActivateRecord', 'RDS', RDSNo)
							ReportRow = ''
							For each Column in Columns using @VM setting cPos
								Locate Column in RDSColumns using @VM setting ConvPos then
									Conv = RDSConvs<0, ConvPos>
									Begin Case
										Case Column EQ 'WO'
											ReportRow<0, cPos> = Field(WMOKey, '*', 1)
										Case Column EQ 'SEQ'
											ReportRow<0, cPos> = RDSNo
										Case Column EQ 'ZONE'
											ReportRow<0, cPos> = Zones<0, SlotNo>
										Case Column EQ 'POCKET'
											ReportRow<0, cPos> = Pockets<0, SlotNo>
										Case Column EQ 'WM_OUT_KEY'
											ReportRow<0, cPos> = WMOKey
										Case Column EQ 'OUT_CASS_NO'
											ReportRow<0, cPos> = Field(WMOKey, '*', 3)
										Case Column EQ 'OUT_SLOT_NO'
											ReportRow<0, cPos> = SlotNo
										Case Column EQ 'SAP_BATCH_NO'
											ReportRow<0, cPos> = SAPBatchNo
										Case Column EQ 'LOT_NUM'
											ReportRow<0, cPos> = Xlate('WO_MAT', WONo:'*':InCassNos<0, SlotNo>, 'LOT_NO', 'X')
										Case Otherwise$
											If Conv EQ '' then
												ReportRow<0, cPos> = Calculate(Column)
											end else
												ReportRow<0, cPos> = OConv(Calculate(Column), Conv)
											end
									End Case
								end
							Next Column
							PrevRDSNo      = RDSNo
							ReportData<-1> = ReportRow
						end
					Next RDSNo
					
				Next WMOKey
			end
			Msg(@WINDOW,MsgUp)
			Swap @VM with ',' in ReportData
			Swap @FM with CRLF$ in ReportData
			Set_Status(0)
			OSWrite ReportData on ExportDir
			ErrCode = ''
			If Get_Status(ErrCode) then
				ErrorMsg = 'Error in ':Service:' service. OSWrite failed! Error code: ':ErrCode:'.'
			end
		end
	end else
		ErrorMsg = 'Error in ':Service:' service. Null DateFrom or EpiPartNo passed in!'
	end
	If ErrorMsg EQ '' then
		Response = ReportData
	end else
		Error_Services('Add', ErrorMsg)
	end
	
end service


Service UpdateDailyMetrics(ReportDate)
	
	If ReportDate NE '' then
		If Not(Num(ReportDate)) then ReportDate = IConv(ReportDate, 'D')
		// Get daily target
		CurrDate     = Date()
		DailyTarget  = 0
		Query        = 'SELECT FISCAL_QTR WITH FISCAL_YR EQ 2022 AND WITH START_DT LT ':Quote(OConv(ReportDate + 1, 'D4/'))
		Query       := ' AND WITH END_DT GT ':Quote(OConv(ReportDate - 1, 'D4/'))
		RList(Query, TARGET_ACTIVELIST$, '', '', '')
		If Not(Get_Status(ErrCode)) then
			ReadNext FiscalQtrKey then
				WorkingDays = Xlate('FISCAL_QTR', FiscalQtrKey, 'PLAN_WORKING_DAYS', 'X')
				Targets     = Xlate('FISCAL_QTR', FiscalQtrKey, 'THRU_TARGET', 'X')
				Qtys        = Xlate('FISCAL_QTR', FiscalQtrKey, 'THRU_QTY', 'X')
				Locate 'Yield_Outs' in Targets using @VM setting vPos then
					QtrTarget   = Qtys<0, vPos>
					DailyTarget = QtrTarget / WorkingDays
					DailyTarget = OConv(IConv(DailyTarget, 'MD0'), 'MD0')
				end
			end
		end
		
		GoSub ClearCursors
		
		// Calculate the metrics for the ReportDate
		OPEN 'DICT.RDS' TO @DICT then
			OPEN 'RDS' TO RDSTable then
				
				TotalThickness24 = 0
				TotalYield24     = 0
				
				
				Dates            = ''
				CurrTime         = Time()
				If CurrTime LT 3600 then
					// 3600 is 1:00 am -> update yesterday's metrics as well
					Dates<-1> = ReportDate - 1
				end
				Dates<-1> = ReportDate
				For each Date in Dates using @FM
					
					TotalThickness  = 0
					WafersOutTotal  = 0
					YieldTotal      = 0
					ScrapTotal      = 0
					ProdTestTotal   = 0
					ProjectedYield  = 0
					YieldPercent    = '0.00'
					ThickWaferRatio = 0
					
					SearchString   = 'DATE_OUT':@VM:OConv(Date, 'D4/')
					RDSKeys        = ''
					Option         = ''
					Flag           = ''
					Set_Status(0)
					Btree.Extract(SearchString, 'RDS', @DICT, RDSKeys, Option, Flag)
					IF Not(Get_Status(errCode)) then
						IF RDSKeys NE '' THEN
							RDSCnt  = COUNT(RDSKeys,@VM) + (RDSKeys NE '')
							LOOP
								@ID = RDSKeys[1,@VM]
								RDSKeys[1,COL2()] = ''
								READ @RECORD FROM RDSTable, @ID THEN
									ReactorsOut     = {WAFERS_IN}
									If Not(Num(ReactorsOut)) then
										// Log the WafersIn value
										LogData     = ''
										LogData<1>  = LoggingDTM
										LogData<2>  = @ID
										LogData<3>  = ReactorsOut
										Logging_Services('AppendLog', objWafersLog, LogData, @RM, @FM)
										ReactorsOut = ReactorsOut<1, 1, 1>
									end
									
									CustScrap       = {CUST_TOT_REJ}
									IFXEpiScrap     = {LSL_TOT_REJ}
									TestKeys        = XLATE('RDS_LAYER', @RECORD<RDS_RDS_LAYER_KEYS$>, 3, 'X')
									TWProd          = obj_RDS_Test('ProdTestCount',TestKeys)
									Reactor         = @RECORD<RDS_REACTOR$>
									ReactorType     = XLATE('REACTOR', Reactor, REACTOR_REACT_TYPE$, 'X')
									Scrap           = CustScrap + IFXEpiScrap
									Yield           = ReactorsOut - Scrap - TWProd
									WafersOutTotal += ReactorsOut
									YieldTotal     += Yield
									ScrapTotal     += Scrap
									ProdTestTotal  += TWProd
									ThickTarget     = OConv({THICK_TARGET_TOT}, 'MD3')
									TotalThickness += (ReactorsOut * ThickTarget)
								end
							UNTIL RDSKeys = ''
							REPEAT
							TotalThickness = TotalThickness[1, 'F.']
						end
					end
					
					If Date EQ CurrDate then
						// Calculate projected yield and projected thickness
						Date             = Date()
						CurrTime         = Time()
						SearchString     = 'DATE_OUT':@VM:OConv((Date - 1), 'D4/'):@FM:'TIME_OUT':@VM:'>':CurrTime
						YesterdayRDSKeys = ''
						Option           = ''
						Flag             = ''
						Set_Status(0)
						Btree.Extract(SearchString, 'RDS', @DICT, YesterdayRDSKeys, Option, Flag)
						If Not(Get_Status(errCode)) then
							SearchString = 'DATE_OUT':@VM:Date
							TodayRDSKeys = ''
							Option       = ''
							Flag         = ''
							Set_Status(0)
							Btree.Extract(SearchString, 'RDS', @DICT, TodayRDSKeys, Option, Flag)
							If Not(Get_Status(errCode)) then
								TotalRDSKeys     = SRP_Array('Join', YesterdayRDSKeys, TodayRDSKeys, 'OR', @VM)
								For each RDS in TotalRDSKeys using @VM
									Database_Services('ActivateRecord', 'RDS', RDS)
									ReactorsOut       = {WAFERS_IN}
									If Not(Num(ReactorsOut)) then
										// Log the WafersIn value
										LogData     = ''
										LogData<1>  = LoggingDTM
										LogData<2>  = RDS
										LogData<3>  = ReactorsOut
										Logging_Services('AppendLog', objWafersLog, LogData, @RM, @FM)
										ReactorsOut = ReactorsOut<1, 1, 1>
									end
									CustScrap         = {CUST_TOT_REJ}
									IFXEpiScrap       = {LSL_TOT_REJ}
									TestKeys          = XLATE('RDS_LAYER', @RECORD<RDS_RDS_LAYER_KEYS$>, 3, 'X')
									TWProd            = obj_RDS_Test('ProdTestCount',TestKeys)
									Reactor           = @RECORD<RDS_REACTOR$>
									ReactorType       = XLATE('REACTOR', Reactor, REACTOR_REACT_TYPE$, 'X')
									Scrap             = CustScrap + IFXEpiScrap
									Yield             = ReactorsOut - Scrap - TWProd
									TotalYield24     += Yield
									TargetThick       = OConv({THICK_TARGET_TOT}, 'MD3')
									TotalThickness24 += TargetThick * ReactorsOut
								Next RDS
								TotalThickness24 = TotalThickness24[1, 'F.']
							end
						end
					end
					
					If WafersOutTotal GT 0 then
						ThickWaferRatio = TotalThickness / WafersOutTotal
						// Round to one decimal 
						ThickWaferRatio = OConv(IConv(ThickWaferRatio, 'MD1'), 'MD1')
						// Round to two decimals
						YieldPercent    = (YieldTotal / WafersOutTotal) * 100
						YieldPercent    = OConv(IConv(YieldPercent, 'MD2'), 'MD2')
					end
					
					If WafersOutTotal   EQ 0 then WafersOutTotal   = ''
					If YieldTotal       EQ 0 then YieldTotal       = ''
					If TotalYield24     EQ 0 then TotalYield24     = ''
					If TotalThickness   EQ 0 then TotalThickness   = ''
					If TotalThickness24 EQ 0 then TotalThickness24 = ''
					
					MetricsRec                                       = ''
					MetricsRec<DAILY_METRICS.WAFERS_OUT$>            = WafersOutTotal
					MetricsRec<DAILY_METRICS.YIELD$>                 = YieldTotal
					MetricsRec<DAILY_METRICS.PROJECTED$>             = TotalYield24
					MetricsRec<DAILY_METRICS.SCRAP$>                 = ScrapTotal
					MetricsRec<DAILY_METRICS.PROD_TEST$>             = ProdTestTotal
					MetricsRec<DAILY_METRICS.TARGET$>                = DailyTarget
					MetricsRec<DAILY_METRICS.TOTAL_THICKNESS$>       = TotalThickness
					MetricsRec<DAILY_METRICS.PROJECTED_THICKNESS$>   = TotalThickness24
					MetricsRec<DAILY_METRICS.THICKNESS_WAFER_RATIO$> = ThickWaferRatio
					MetricsRec<DAILY_METRICS.YIELD_PERCENT$>         = YieldPercent
					Database_Services('WriteDataRow', 'DAILY_METRICS', Date, MetricsRec)
					
				Next Date
			end
		end
	end
	
end service


Service GetMetricsJSON(Date)
	
	// Get a week's worth of metrics and return them as JSON
	StartDate    = Date() - 6
	EndDate      = Date()
	
	JSON           = ''
	hMetricsObj    = ''
	hMetricsArray  = ''
	hDayMetricsObj = ''
	hStatusObj     = ''
	If SRP_JSON(hMetricsObj, 'NEW', 'OBJECT') then
		If SRP_JSON(hMetricsArray, 'NEW', 'ARRAY') then
			DateIndex = 1
			For Date = StartDate to EndDate
				MetricsRec = Database_Services('ReadDataRow', 'DAILY_METRICS', Date)
				// Create new date object
				SRP_JSON(hDayMetricsObj, 'NEW', 'OBJECT')
				// Add values
				SRP_JSON(hDayMetricsObj, 'SETVALUE', 'date', OConv(Date, 'D4/'))
				SRP_JSON(hDayMetricsObj, 'SETVALUE', 'target', MetricsRec<DAILY_METRICS.TARGET$>)
				SRP_JSON(hDayMetricsObj, 'SETVALUE', 'reactorOut', MetricsRec<DAILY_METRICS.WAFERS_OUT$>)
				SRP_JSON(hDayMetricsObj, 'SETVALUE', 'yieldOut', MetricsRec<DAILY_METRICS.YIELD$>)
				SRP_JSON(hDayMetricsObj, 'SETVALUE', 'projected', MetricsRec<DAILY_METRICS.PROJECTED$>)
				SRP_JSON(hDayMetricsObj, 'SETVALUE', 'yieldPercent', MetricsRec<DAILY_METRICS.YIELD_PERCENT$>, 'String')
				// Add object to metrics array
				SRP_JSON(hMetricsArray, 'ADD', hDayMetricsObj)
				SRP_JSON(hDayMetricsObj, 'RELEASE')
			Next Date
		end
		SRP_JSON(hMetricsObj, 'SET', 'dailyMetrics', hMetricsArray)
		JSON    = SRP_JSON(hMetricsObj, 'STRINGIFY', 'FAST')
		SRP_JSON(hMetricsArray, 'RELEASE')
		SRP_JSON(hStatusObj, 'RELEASE')
	end else
		Error_Services('Add', 'Error creating the metrics JSON object.')
	end
	
	Response = JSON
	
end service


Service GetThickMetricsJSON(Date)
	
	// Get a week's worth of metrics and return them as JSON
	StartDate    = Date() - 6
	EndDate      = Date()
	
	JSON           = ''
	hMetricsObj    = ''
	hMetricsArray  = ''
	hDayMetricsObj = ''
	If SRP_JSON(hMetricsObj, 'NEW', 'OBJECT') then
		If SRP_JSON(hMetricsArray, 'NEW', 'ARRAY') then
			DateIndex = 1
			For Date = StartDate to EndDate
				MetricsRec = Database_Services('ReadDataRow', 'DAILY_METRICS', Date)
				// Create new date object
				SRP_JSON(hDayMetricsObj, 'NEW', 'OBJECT')
				// Add values
				SRP_JSON(hDayMetricsObj, 'SETVALUE', 'date', OConv(Date, 'D4/'))
				SRP_JSON(hDayMetricsObj, 'SETVALUE', 'thickness', MetricsRec<DAILY_METRICS.TOTAL_THICKNESS$>)
				SRP_JSON(hDayMetricsObj, 'SETVALUE', 'projectedThickness', MetricsRec<DAILY_METRICS.PROJECTED_THICKNESS$>)
				SRP_JSON(hDayMetricsObj, 'SETVALUE', 'thicknessWaferRatio', MetricsRec<DAILY_METRICS.THICKNESS_WAFER_RATIO$>)
				// Add object to metrics array
				SRP_JSON(hMetricsArray, 'ADD', hDayMetricsObj)
				SRP_JSON(hDayMetricsObj, 'RELEASE')
			Next Date
		end
		SRP_JSON(hMetricsObj, 'SET', 'dailyMetrics', hMetricsArray)
		JSON    = SRP_JSON(hMetricsObj, 'STRINGIFY', 'FAST')
		SRP_JSON(hMetricsArray, 'RELEASE')
		SRP_JSON(hStatusObj, 'RELEASE')
	end else
		Error_Services('Add', 'Error creating the metrics JSON object.')
	end
	
	Response = JSON
	
end service


Service GetMaterialTrackJSON()
	
	ErrCode = ''
	KeyList = ''
	Query   = 'SELECT MATERIAL_REPORT BY REACT_NO BY START_DTM'
	RList(Query, TARGET_ACTIVELIST$, '', '', '')
	If Not(Get_Status(ErrCode)) then
		EOF = False$
		Loop
			ReadNext KeyId else EOF = True$
		Until EOF
			KeyList<-1> = KeyId
		Repeat
		If KeyList NE '' then
			MatRepJSON = ''
			If SRP_JSON(objJSON, 'New', 'Object') then
				If SRP_JSON(objReactArray, 'New', 'Array') then
					For each KeyId in KeyList using @FM setting fPos
						Row          = Database_Services('ReadDataRow', 'MATERIAL_REPORT', KeyId)
						If Error_Services('NoError') then
							KitRunOrder  = Row<MATERIAL_REPORT.KIT_RO$>
							PTIRunOrder  = Row<MATERIAL_REPORT.PTI_RO$>
							LoadRunOrder = Row<MATERIAL_REPORT.LOAD_RO$>
							If KitRunOrder NE '' or PTIRunOrder NE '' or LoadRunOrder NE '' then
								If SRP_JSON(objRow, 'New', 'Object') then
									SRP_JSON(objRow, 'SetValue', 'KeyId', KeyId)
									SRP_JSON(objRow, 'SetValue', 'ReactorNo', Row<MATERIAL_REPORT.REACT_NO$>)
									SRP_JSON(objRow, 'SetValue', 'ReactorType', Row<MATERIAL_REPORT.REACT_TYPE$>)
									SRP_JSON(objRow, 'SetValue', 'WorkOrderNo', Row<MATERIAL_REPORT.WO_NO$>)
									SRP_JSON(objRow, 'SetValue', 'SapProdNo', Row<MATERIAL_REPORT.PROD_ORD_NO$>)
									SRP_JSON(objRow, 'SetValue', 'SubPartNo', Row<MATERIAL_REPORT.SUB_PART_NO$>)
									SRP_JSON(objRow, 'SetValue', 'EpiPartNo', Row<MATERIAL_REPORT.EPI_PART_NO$>)
									WOQty     = Row<MATERIAL_REPORT.WO_QTY$>
									Swap ',' with '' in WOQty
									SRP_JSON(objRow, 'SetValue', 'WoQty', WOQty)
									RxQty     =  Row<MATERIAL_REPORT.WO_RX_QTY$>
									Swap ',' with '' in RxQty
									SRP_JSON(objRow, 'SetValue', 'RxQty', RxQty)
									UnRelQty  = Row<MATERIAL_REPORT.WO_UNREL_QTY$>
									Swap ',' with '' in UnRelQty
									SRP_JSON(objRow, 'SetValue', 'UnRelQty', UnRelQty)
									SRP_JSON(objRow, 'SetValue', 'KitLoc', Row<MATERIAL_REPORT.KIT_LOCATION$>)
									KitQty    = Row<MATERIAL_REPORT.KIT_QTY$>
									Swap ',' with '' in KitQty
									SRP_JSON(objRow, 'SetValue', 'KitQty', KitQty)
									SRP_JSON(objRow, 'SetValue', 'CassDelta', Row<MATERIAL_REPORT.CASS_DELTA$>)
									Swap ',' with ', ' in KitRunOrder
									SRP_JSON(objRow, 'SetValue', 'KitRunOrder', KitRunOrder)
									KitDemand = Row<MATERIAL_REPORT.KIT_DEMAND$>
									Swap ',' with ', ' in KitDemand
									SRP_JSON(objRow, 'SetValue', 'KitDemand', KitDemand)
									Swap ',' with ', ' in PTIRunOrder
									SRP_JSON(objRow, 'SetValue', 'PtiRunOrder', PTIRunOrder)
									Swap ',' with ', ' in LoadRunOrder
									SRP_JSON(objRow, 'SetValue', 'LoadRunOrder', LoadRunOrder)
									SRP_JSON(objRow, 'SetValue', 'Comments', Row<MATERIAL_REPORT.COMMENTS$>)
									SRP_JSON(objReactArray, 'Add', objRow)
									SRP_JSON(objRow, 'Release')
								end
							end							
						end
					Next KeyId
					SRP_JSON(objJSON, 'Set', 'MaterialReport', objReactArray)
					SRP_JSON(objReactArray, 'Release')
				end
				Response = SRP_JSON(objJSON, 'Stringify', 'Styled')
				SRP_JSON(objJSON, 'Release')
			end  		
		end else
			Error_Services('Add', 'Error in ':Service:' service. No rows returned.')
		end		
	end else
		Error_Services('Add', 'Error in ':Service:' service. Error calling RList. Error message: ':ErrCode)
	end
	
end service


Service CalcDailyPerformanceData(DateOut)
	
	hSysLists  = Database_Services('GetTableHandle', 'SYSLISTS')
	Lock hSysLists, ServiceKeyID then
		LogData = ''
		LogData<1> = LoggingDtm
		LogData<2> = '1'
		LogData<3> = 'Begin service.'
		Logging_Services('AppendLog', objReportsLog, LogData, @RM, @FM)
		
		Response   = ''
		CurrDate   = Date()
		CurrDTM    = Datetime()
		ReportDate = IConv(DateOut, 'D')
		ReportData = ''
		
		// Clear out previous daily_performance metrics
		table_name = "DAILY_PERFORMANCE"
		flag       = ""
		done       = False$
		CursorVar  = ""
		
		For counter = 0 To 8
			ClearSelect counter
		Next counter
		
		sort_list     = "DATE_OUT"
		Reduce_Script = "WITH {DATE_OUT} EQ '":OConv(ReportDate, 'D4/'):"'"
		mode          = NEXT_CUR$
		DeleteKeyList = ''
		LogData = ''
		LogData<1>    = LoggingDtm
		LogData<2> = '2'
		LogData<3> = 'Calling Reduce with query "':Reduce_Script:'" using next available cursor.'
		Logging_Services('AppendLog', objReportsLog, LogData, @RM, @FM)
		
		Reduce(reduce_script, sort_list, mode, table_name, Cursorvar, flag)
		If flag then
			Select table_name By sort_list Using Cursorvar then
				Open table_name To file_var then
					Loop
						ReadNext KeyID Using Cursorvar By AT Else done = TRUE$
					Until done
						DeleteKeyList<-1> = KeyID
					Repeat
				End
			end
		End
		LogData = ''
		LogData<1> = LoggingDtm
		LogData<2> = '3'
		LogData<3> = 'Deleting selected records.'
		Logging_Services('AppendLog', objReportsLog, LogData, @RM, @FM)
		
		If DeleteKeyList NE '' then
			For each KeyID in DeleteKeyList using @FM
				Database_Services('DeleteDataRow', 'DAILY_PERFORMANCE', KeyID, True$, False$)
			Next KeyID
		end
		
		// Begin by building DAILY_PERFORMANCE data from RDS table. We will need to supplement this below
		// for work orders / parts that do not have any cassettes unloaded yet.
		Query      = 'SELECT RDS WITH DATE_OUT EQ ':Quote(OConv(ReportDate, 'D4/')): ' BY REACTOR'
		EOF        = False$
		ErrCode    = ''
		ReportData = ''
		RawData    = ''
		
		// Clear cursors for good measure
		For counter = 0 To 8
			ClearSelect counter
		Next counter
		LogData = ''
		LogData<1> = LoggingDtm
		LogData<2> = '4'
		LogData<3> = 'Selecting RDS records using query "':Query:'".'
		Logging_Services('AppendLog', objReportsLog, LogData, @RM, @FM)
		
		RList(Query, TARGET_ACTIVELIST$, '', '', '')
		RowIndex   = 0
		If Not(Get_Status(ErrCode)) then
			Loop
				ReadNext KeyID else EOF = True$
			Until EOF
				RowIndex += 1
				Database_Services('ActivateRecord', 'RDS', KeyID)
				If Error_Services('NoError') then
					Reactor                                   = {REACTOR}
					PSN                                       = {PROD_SPEC_ID}
					DateOut                                   = {DATE_OUT}
					PartNo                                    = {PART_NUM}
					WafersOut                                 = {WAFERS_IN}
					DtmIn                                     = {DTM_IN}
					DtmOut                                    = {DTM_OUT}
					ReportKey                                 = DateOut:'*':Reactor:'*':PSN:'*':PartNo
					ReportRow                                 = Database_Services('ReadDataRow', 'DAILY_PERFORMANCE', ReportKey)
					RepWafersOut                              = ReportRow<DAILY_PERFORMANCE.WAFERS_OUT$>
					RepWafersOut                             += WafersOut
					ReportRow<DAILY_PERFORMANCE.WAFERS_OUT$>  = RepWafersOut
					Database_Services('WriteDataRow', 'DAILY_PERFORMANCE', ReportKey, ReportRow)
				end
			Repeat
		end
		
		Reactors = Reactor_Services('GetReactorNumbers')
		RowIndex = 0
		For each ReactorNo in Reactors using @FM setting RIndex
			ReactorAssign = Xlate('REACTOR', ReactorNo, 'REACT_ASSIGNMENT', 'X')
			// Only report reactors that are not out of service.
			If ReactorAssign NE 'O' then
				KeyList          = ''
				ReportRow        = ''
				ReportRow<0, 1>  = ReactorNo
				
				// Supplement DAILY_PERFORMANCE data for work orders / parts that do not have any cassettes unloaded yet.
				// Get the scheduled PSN, PartNo, and ReactType
				SchedWONos             = ''
				SchedPSNs              = ''
				SchedEpiParts          = ''
				SchedDetKeyIDs         = ''
				// The below EpiPro reactors are scheduled via a "sister" reactor (e.g. 44 for 46)
				If ( (ReactorNo EQ 42) or (ReactorNo EQ 46) or (ReactorNo EQ 50) or (ReactorNo EQ 54) ) then
					QueryReactorNo = ReactorNo - 2
				end else
					QueryReactorNo = ReactorNo
				end
				Query = "SELECT SCHED_DET_NG WITH REACT_NO EQ ":QueryReactorNo:" AND WITH BLOCK_OUT NE 1 AND WITH START_DTM LT '":OConv(ReportDate + 1, 'DT2/^H'):"' AND WITH STOP_DTM GT '":OConv(ReportDate, 'DT2/^H'):"'"
				LogData = ''
				LogData<1> = LoggingDtm
				LogData<2> = '5.':RIndex:'.1'
				LogData<3> = 'Selecting SCHED_DET_NG records for reactor ':ReactorNo:' using query "':Query:'".'
				Logging_Services('AppendLog', objReportsLog, LogData, @RM, @FM)
				
				// Clear cursors for good measure.
				For counter = 0 To 8
					ClearSelect counter
				Next counter
				
				RList(Query, TARGET_ACTIVELIST$, '', '', '')
				If Not(Get_Status(ErrCode)) then
					EOF = False$
					Loop
						ReadNext SchedKeyID else EOF = True$
					Until EOF
						SchedDetKeyIDs<0, -1> = SchedKeyID
					Repeat
				end
				LogData = ''
				LogData<1> = LoggingDtm
				LogData<2> = '5.':RIndex:'.2'
				LogData<3> = 'Reading SCHED_DET_NG records for reactor ':ReactorNo:'.'
				Logging_Services('AppendLog', objReportsLog, LogData, @RM, @FM)                
				
				If SchedDetKeyIDs NE '' then
					If Error_Services('NoError') then
						HoursUp   = ''
						SchedKeys = ''
						For Each SchedDetKeyID in SchedDetKeyIDs using @VM setting vPos
							SchedDetRec      = Database_Services('ReadDataRow', 'SCHED_DET_NG', SchedDetKeyID)
							SchedWONo        = SchedDetRec<SCHED_DET_NG.WO_NO$>
							SchedStartDtm    = SchedDetRec<SCHED_DET_NG.START_DTM$>
							SchedStopDtm     = SchedDetRec<SCHED_DET_NG.STOP_DTM$>
							SchedStartDt     = SchedStartDTM[1, 'F.']
							SchedStopDt      = SchedStopDTM[1, 'F.']
							Begin Case
								Case SchedStopDt EQ ReportDate
									// This scheduled part is ending
									If SchedStopDtm GT CurrDtm then
										HoursUp<0, vPos> = (CurrDtm - ReportDate) * 24
									end else
										HoursUp<0, vPos> = (SchedStopDtm - ReportDate) * 24
									end
								Case SchedStartDt EQ ReportDate
									// This scheduled part is starting on the day of the report
									Begin Case
										Case SchedStartDtm GT CurrDtm 
											TempHours = 0
										Case ReportDate EQ CurrDate
											TempHours = ( ( CurrDtm - SchedStartDtm) * 24 )
										Case Otherwise$
											TempHours = ( ( (ReportDate + 1) - SchedStartDtm) * 24 )
									End Case
									If TempHours LT 0 then TempHours = 0
									HoursUp<0, vPos> = TempHours
								Case ReportDate EQ CurrDate
									// Part is still running. Calculate how long it has been up today.
									HoursUp<0, vPos> = (CurrDtm - ReportDate) * 24
								Case Otherwise$
									HoursUp<0, vPos> = 24
							End Case
							SchedWONos<0, -1>    = SchedWONo
							SchedPSN             = Xlate('WO_LOG', SchedWONo, 'PS_NO', 'X')
							SchedPSNs<0, -1>     = SchedPSN
							SchedEpiParts<0, -1> = Xlate('WO_LOG', SchedWONo, 'EPI_PART_NO', 'X')
							
							SchedKeys<0, -1>     = SchedDetKeyID
						Next SchedDetKeyID
					end
				end
				LogData = ''
				LogData<1> = LoggingDtm
				LogData<2> = '5.':RIndex:'.3'
				LogData<3> = 'Writing new DAILY_PERFORMANCE records reactor ':ReactorNo:'.'
				Logging_Services('AppendLog', objReportsLog, LogData, @RM, @FM)    
				
				RepEpiParts = SRP_Array('Clean', SchedEpiParts, 'TrimAndMakeUnique', @VM)
				If RepEpiParts NE '' then
					For each SchedEpiPart in RepEpiParts using @VM setting vPos
						If SchedEpiPart NE '' then
							Locate SchedEpiPart in SchedEpiParts using @VM setting sPos then
								SchedPSN = SchedPSNs<0, sPos>
							end
						end else
							SchedPSN = ''
						end
						PSN                                           = SchedPSN
						PartNo                                        = SchedEpiPart
						PerfKey                                       = DateOut:'*':ReactorNo:'*':PSN:'*':PartNo
						PerfRow                                       = Database_Services('ReadDataRow', 'DAILY_PERFORMANCE', PerfKey)
						RepWafersOut                                  = PerfRow<DAILY_PERFORMANCE.WAFERS_OUT$>
						If RepWafersOut EQ '' then RepWafersOut  = 0
						PerfRow<DAILY_PERFORMANCE.WAFERS_OUT$>        = RepWafersOut
						PerfRow<DAILY_PERFORMANCE.HOURS_ON_PART$>     = OConv(IConv(HoursUp<0, vPos>, 'MD2'), 'MD2')
						PerfRow<DAILY_PERFORMANCE.SCHED_DET_KEY_IDS$> = SchedKeys<0, vPos>
						Database_Services('WriteDataRow', 'DAILY_PERFORMANCE', PerfKey, PerfRow)
					Next SchedEpiPart
				end
				// Merge in Changeover data and build the report row from the DAILY_PERFORMANCE data generated above.
				
				table_name = "DAILY_PERFORMANCE"
				flag       = ""
				done       = False$
				CursorVar  = ""
				
				* Clears all cursors
				For counter = 0 To 8
					ClearSelect counter
				Next counter
				
				sort_list     = "DATE_OUT"
				Reduce_Script = "WITH {REACTOR} EQ '":ReactorNo:"' AND WITH {DATE_OUT} EQ '":OConv(ReportDate, 'D4/'):"'"
				mode          = NEXT_CUR$
				LogData = ''
				LogData<1> = LoggingDtm
				LogData<2> = '5.':RIndex:'.4'
				LogData<3> = 'Selecting new DAILY_PERFORMANCE records for reactor ':ReactorNo:' using query "':Reduce_Script:'" via Reduce.'
				Logging_Services('AppendLog', objReportsLog, LogData, @RM, @FM) 
				
				Reduce(reduce_script, sort_list, mode, table_name, Cursorvar, flag)
				If flag then
					Select table_name By sort_list Using Cursorvar then
						Open table_name To file_var then
							Loop
								ReadNext KeyID Using Cursorvar By AT Else done = TRUE$
							Until done
								KeyList<-1> = KeyID
							Repeat
						End
					end
				End
				LogData = ''
				LogData<1> = LoggingDtm
				LogData<2> = '5.':RIndex:'.5'
				LogData<3> = 'Updating new DAILY_PERFORMANCE records for reactor ':ReactorNo:'.'
				Logging_Services('AppendLog', objReportsLog, LogData, @RM, @FM)                 
				
				If KeyList NE '' then
					// Build the final report row with data compiled above.
					For each KeyID in KeyList using @FM setting fPos
						WafersOut           = Xlate('DAILY_PERFORMANCE', KeyID, 'WAFERS_OUT', 'X')
						PSN                 = Field(KeyID, '*', 3)
						PartNo              = Field(KeyID, '*', 4)
						ReactType           = Xlate('REACTOR', ReactorNo, 'REACT_TYPE', 'X')
						If ReactType EQ 'EPP' then ReactType = 'EPIPRO'
						ReportRow<0, 2, -1> = PSN
						ReportRow<0, 3, -1> = PartNo
						HoursOnPart         = Xlate('DAILY_PERFORMANCE', KeyID, 'HOURS_ON_PART', 'X')
						ReportRow<0, 4, -1> = OConv(IConv(HoursOnPart, 'MD2'), 'MD2')
						ReportRow<0, 5, -1> = WafersOut           ; // ActualOuts
						ReportRow<0, 6, -1> = XLATE('DAILY_PERFORMANCE', KeyID, DAILY_PERFORMANCE.SCHED_DET_KEY_IDS$, 'X')
						ReportRow<0, 7>     = ReactorNo
					Next KeyID
					ReportData<-1> = ReportRow
				end
			end
			
		Next ReactorNo
		LogData = ''
		LogData<1> = LoggingDtm
		LogData<2> = '6'
		LogData<3> = 'Begin building DAILY_PERFORMANCE_REPORT rows.'
		Logging_Services('AppendLog', objReportsLog, LogData, @RM, @FM)         
		
		ReportKeys = ''
		For each Row in ReportData using @FM setting fPos
			LogData = ''
			LogData<1> = LoggingDtm
			LogData<2> = '6.':fPos
			LogData<3> = 'Building DAILY_PERFORMANCE_REPORT row ':fPos:'.'
			Logging_Services('AppendLog', objReportsLog, LogData, @RM, @FM) 
			
			PSNs    = Row<0, 2>
			NumPSNs = DCount(Row<0, 2>, @SVM)
			If NumPSNs GT 0 then
				For PSNIndex = 1 to NumPSNs
					KeyID             = ReportDate:'*':Row<0, 1>:'*':Row<0, 2, PSNIndex>:'*':Row<0, 3, PSNIndex>
					ReportKeys<0, -1> = KeyID
					If RowExists('DAILY_PERFORMANCE_REPORT', KeyID) then
						Rec = Database_Services('ReadDataRow', 'DAILY_PERFORMANCE_REPORT', KeyID)
					end else
						Rec = ''
					end
					Rec<DAILY_PERFORMANCE_REPORT.HOURS_ON_PART$> = Row<0, 4, PSNIndex>
					Rec<DAILY_PERFORMANCE_REPORT.ACTUAL_OUT$>    = Row<0, 5, PSNIndex>
					ActualStartDTM                               = ''
					ActualStopDTM                                = ''
					SchedDetRec                                  = Database_Services('ReadDataRow', 'SCHED_DET_NG', Row<0, 6, PSNIndex>)
					SchedDetStartDTM                             = SchedDetRec<SCHED_DET_NG.START_DTM$>
					SchedDetStopDTM                              = SchedDetRec<SCHED_DET_NG.STOP_DTM$>
					If SchedDetStartDTM LT ReportDate then
						ActualStartDTM = ReportDate
					end else
						ActualStartDTM = SchedDetStartDTM
					end
					If SchedDetStopDTM GT ReportDate + 1 then
						ActualStopDTM = ReportDate + 1
					end else
						ActualStopDTM = SchedDetStopDTM
					end
					Rec<DAILY_PERFORMANCE_REPORT.START_DTM$>     = ActualStartDTM
					Rec<DAILY_PERFORMANCE_REPORT.STOP_DTM$>      = ActualStopDTM
					
					//Get Mode Metric here
					EQU PRODUCTIVE$                                      To 1
					EQU UNSCHED$                                         to 2
					EQU SCHED$                                           to 3
					TotalReactorData                                      = Reactor_Services('GetReactorUptimeMetricsByTimeSpan', Row<0, 7> , ActualStartDTM, ActualStopDTM)
					Rec<DAILY_PERFORMANCE_REPORT.TOTAL_PROD_PERCENT$>     = TotalReactorData<1, PRODUCTIVE$>
					Rec<DAILY_PERFORMANCE_REPORT.TOTAL_PROD_MIN$>         = TotalReactorData<2, PRODUCTIVE$>
					Rec<DAILY_PERFORMANCE_REPORT.TOTAL_UNSCHED_PERCENT$>  = TotalReactorData<1, UNSCHED$>
					Rec<DAILY_PERFORMANCE_REPORT.TOTAL_UNSCHED_MIN$>      = TotalReactorData<2, UNSCHED$>
					Rec<DAILY_PERFORMANCE_REPORT.TOTAL_SCHED_MIN$>        = TotalReactorData<2, SCHED$>
					Rec<DAILY_PERFORMANCE_REPORT.TOTAL_SCHED_PERCENT$>    = TotalReactorData<1, SCHED$>
					
					Database_Services('WriteDataRow', 'DAILY_PERFORMANCE_REPORT', KeyID, Rec)
				Next PSNIndex
			end else
				// Nothing ran or is scheduled on this reactor
				KeyID             = ReportDate:'*':Row<0, 1>:'*':Row<0, 2, PSNIndex>:'*':Row<0, 3, PSNIndex>
				ReportKeys<0, -1> = KeyID
				If RowExists('DAILY_PERFORMANCE_REPORT', KeyID) then
					Rec = Database_Services('ReadDataRow', 'DAILY_PERFORMANCE_REPORT', KeyID)
				end else
					Rec = ''
				end
				Rec<DAILY_PERFORMANCE_REPORT.HOURS_ON_PART$> = 0
				Rec<DAILY_PERFORMANCE_REPORT.ACTUAL_OUT$>    = 0
				Database_Services('WriteDataRow', 'DAILY_PERFORMANCE_REPORT', KeyID, Rec)
			end
		Next Row 
		
		// Clean out any rows that weren't just generated.
		table_name = "DAILY_PERFORMANCE_REPORT"
		flag      = ""
		done      = False$
		CursorVar = ""
		* Clears all cursors
		For counter = 0 To 8
			ClearSelect counter
		Next counter
		sort_list     = "DATE_OUT"
		Reduce_Script = "WITH {DATE_OUT} EQ '":OConv(ReportDate, 'D4/'):"'"
		mode          = NEXT_CUR$
		LogData = ''
		LogData<1> = LoggingDtm
		LogData<2> = '7'
		LogData<3> = 'Selecing DAILY_PERFORMANCE_REPORT rows to delete using query "':Reduce_Script:'" via Reduce.'
		Logging_Services('AppendLog', objReportsLog, LogData, @RM, @FM) 
		
		Reduce(reduce_script, sort_list, mode, table_name, Cursorvar, flag)
		If flag then
			Select table_name By sort_list Using Cursorvar then
				Open table_name To file_var then
					Loop
						ReadNext KeyID Using Cursorvar By AT Else done = TRUE$
					Until done
						Locate KeyID in ReportKeys using @VM setting vPos else
							Database_Services('DeleteDataRow', 'DAILY_PERFORMANCE_REPORT', KeyID)
						end
					Repeat
				End
			end
		end
		LogData = ''
		LogData<1> = LoggingDtm
		LogData<2> = '8'
		LogData<3> = 'End service.'
		Logging_Services('AppendLog', objReportsLog, LogData, @RM, @FM) 
		
		Unlock hSysLists, ServiceKeyID else Null
	end
	
end service


Service GenerateOrUpdateDailyReactorUptimeData()
    
	//Generates new or updates existing Reactor uptime data for all reactors in the current day.
	hSysLists  = Database_Services('GetTableHandle', 'SYSLISTS')
	Lock hSysLists, ServiceKeyID then
		Today = Date()
		DataArray = ''
		//ASM Reactors
		ASMRecordKey = 'ASM*': Today
		ASMUptimeRec = Reactor_Services('GetReactorUptimeMetricsByTypeAndTimeSpan', Today, Today + 1, 'ASM')
		Database_Services('WriteDataRow', 'REACTOR_DAILY_UPTIME', ASMRecordKey, ASMUptimeRec, True$, False$, True$)
		//HTR Reactors
		HTRRecordKey = 'HTR*': Today
		HTRUptimeRec = Reactor_Services('GetReactorUptimeMetricsByTypeAndTimeSpan', Today, Today + 1, 'HTR')
		Database_Services('WriteDataRow', 'REACTOR_DAILY_UPTIME', HTRRecordKey, HTRUptimeRec, True$, False$, True$)
		//EPP Reactors
		EPPRecordKey = 'EPP*': Today
		EPPUptimeRec = Reactor_Services('GetReactorUptimeMetricsByTypeAndTimeSpan', Today, Today + 1, 'EPP')
		Database_Services('WriteDataRow', 'REACTOR_DAILY_UPTIME', EPPRecordKey, EPPUptimeRec, True$, False$, True$)
		//All Reactors
		ALLRecordKey = 'ALL*': Today
		ALLUptimeRec = Reactor_Services('GetReactorUptimeMetricsByTypeAndTimeSpan', Today, Today + 1, 'ALL')
		Database_Services('WriteDataRow', 'REACTOR_DAILY_UPTIME', ALLRecordKey, ALLUptimeRec, True$, False$, True$)
		Unlock hSysLists, ServiceKeyID else Null
	end
	
end service


Service GetDailyReactorUptimeDataJSON(ReportStartDt, ReportEndDt)
    
	Response = ''
	Begin Case
		Case ReportStartDt EQ ''
			Error_Services('Add', 'Start Date was not provided.')
		Case ReportEndDt EQ ''
			Error_Services('Add', 'End Date was not provided.')
	End Case
	ReportStartDT = IConv(ReportStartDt, 'D4/H')
	ReportEndDt = IConv(ReportEndDt, 'D4/H')
	//Calculate Days to include
	FirstDay = SRP_Datetime('Date', ReportStartDt)
	LastDay = SRP_Datetime('Date', ReportEndDt) - 1
	DataArray = ''
	ASMPeriodUptimeMinutes = 0
	ASMPeriodAvailMinutes = 0
	HTRPeriodUptimeMinutes = 0
	HTRPeriodAvailMinutes = 0
	EPPPeriodUptimeMinutes = 0
	EPPPeriodAvailMinutes = 0
	AllPeriodUptimeMinutes = 0
	AllPeriodAvailMinutes = 0
	ReactorUptimeJson = ''
	//Create Root Object
	If SRP_Json(ReactorUptimeJson, 'New') then
		//Create an Array of days
		ReactorUptimeDaysJson = ''
		If SRP_Json(ReactorUptimeDaysJson, 'New', 'Array') then
			//For each day create a daily uptime object
			for day = FirstDay to LastDay
				//Single Day Object
				ASMUptimeKey = 'ASM*':day
				HTRUptimeKey = 'HTR*':day
				EPPUptimeKey = 'EPP*':day
				AllUptimeKey = 'ALL*':day
				ReactorUptimeDayJson = ''
				If SRP_Json(ReactorUptimeDayJson, 'New') then
					ASMUptime = 0
					HTRUptime = 0
					EPPUptime = 0
					AllUptime = 0
					If RowExists('REACTOR_DAILY_UPTIME', ASMUptimeKey) then
						ASMUptimeRec = Database_Services('ReadDataRow', 'REACTOR_DAILY_UPTIME', ASMUptimeKey, True$, 0)
						ASMUptime = ASMUptimeRec<REACTOR_DAILY_UPTIME_TOTAL_UPTIME_PERCENTAGE$>
						ASMUptime = SRP_Math('ROUND', ASMUptime * 100, 2)
						ASMUptimeMinutes = ASMUptimeRec<REACTOR_DAILY_UPTIME_TOTAL_UPTIME_MINUTES$>
						ASMAvailMinutes = ASMUptimeRec<REACTOR_DAILY_UPTIME_TOTAL_AVAIL_MINUTES$>
						ASMPeriodUptimeMinutes += ASMUptimeMinutes
						ASMPeriodAvailMinutes += ASMAvailMinutes
					end
					If RowExists('REACTOR_DAILY_UPTIME', HTRUptimeKey) then
						HTRUptimeRec = Database_Services('ReadDataRow', 'REACTOR_DAILY_UPTIME', HTRUptimeKey, True$, 0)
						HTRUptime = HTRUptimeRec<REACTOR_DAILY_UPTIME_TOTAL_UPTIME_PERCENTAGE$>
						HTRUptime = SRP_Math('ROUND', HTRUptime * 100, 2)
						HTRUptimeMinutes = HTRUptimeRec<REACTOR_DAILY_UPTIME_TOTAL_UPTIME_MINUTES$>
						HTRAvailMinutes = HTRUptimeRec<REACTOR_DAILY_UPTIME_TOTAL_AVAIL_MINUTES$>
						HTRPeriodUptimeMinutes += HTRUptimeMinutes
						HTRPeriodAvailMinutes += HTRAvailMinutes
					end
					If RowExists('REACTOR_DAILY_UPTIME', EPPUptimeKey) then
						EPPUptimeRec = Database_Services('ReadDataRow', 'REACTOR_DAILY_UPTIME', EPPUptimeKey, True$, 0)
						EPPUptime = EPPUptimeRec<REACTOR_DAILY_UPTIME_TOTAL_UPTIME_PERCENTAGE$>
						EPPUptime = SRP_Math('ROUND', EPPUptime * 100, 2)
						EPPUptimeMinutes = EPPUptimeRec<REACTOR_DAILY_UPTIME_TOTAL_UPTIME_MINUTES$>
						EPPAvailMinutes = EPPUptimeRec<REACTOR_DAILY_UPTIME_TOTAL_AVAIL_MINUTES$>
						EPPPeriodUptimeMinutes += EPPUptimeMinutes
						EPPPeriodAvailMinutes +=	EPPAvailMinutes
					end
					If RowExists('REACTOR_DAILY_UPTIME', AllUptimeKey) then
						AllUptimeRec = Database_Services('ReadDataRow', 'REACTOR_DAILY_UPTIME', AllUptimeKey, True$, 0)
						AllUptime = AllUptimeRec<REACTOR_DAILY_UPTIME_TOTAL_UPTIME_PERCENTAGE$>
						AllUptime = SRP_Math('ROUND', AllUptime * 100, 2)
						AllUptimeMinutes = AllUptimeRec<REACTOR_DAILY_UPTIME_TOTAL_UPTIME_MINUTES$>
						AllAvailMinutes = AllUptimeRec<REACTOR_DAILY_UPTIME_TOTAL_AVAIL_MINUTES$>
						AllPeriodUptimeMinutes += AllUptimeMinutes
						AllPeriodAvailMinutes += AllAvailMinutes
					end
					SRP_Json(ReactorUptimeDayJson, "SetValue", "ReportDate", Oconv(day, 'DT4/H'))
					SRP_Json(ReactorUptimeDayJson, "SetValue", "ASMUptime", ASMUptime)
					SRP_Json(ReactorUptimeDayJson, "SetValue", "ASMUptimeMinutes", ASMUptimeMinutes)
					SRP_Json(ReactorUptimeDayJson, "SetValue", "ASMAvailMinutes", ASMAvailMinutes)
					SRP_Json(ReactorUptimeDayJson, "SetValue", "HTRUptime", HTRUptime)
					SRP_Json(ReactorUptimeDayJson, "SetValue", "HTRUptimeMinutes", HTRUptimeMinutes)
					SRP_Json(ReactorUptimeDayJson, "SetValue", "HTRAvailMinutes", HTRAvailMinutes)
					SRP_Json(ReactorUptimeDayJson, "SetValue", "EPPUptime", EPPUptime)
					SRP_Json(ReactorUptimeDayJson, "SetValue", "EPPUptimeMinutes", EPPUptimeMinutes)
					SRP_Json(ReactorUptimeDayJson, "SetValue", "EPPAvailMinutes", EPPAvailMinutes)
					SRP_Json(ReactorUptimeDayJson, "SetValue", "AllUptime", AllUptime)
					SRP_Json(ReactorUptimeDayJson, "SetValue", "AllUptimeMinutes", ALLUptimeMinutes)
					SRP_Json(ReactorUptimeDayJson, "SetValue", "AllAvailMinutes", ALLAvailMinutes)
					SRP_Json(ReactorUptimeDaysJson, "Add", ReactorUptimeDayJson)
					//Release it
					SRP_Json(ReactorUptimeDayJson, "Release")
					
					
				end
			Next day
			//Add array to root object
			SRP_Json(ReactorUptimeJson, 'Set', 'Days', ReactorUptimeDaysJson)
			//Close out array
			SRP_Json(ReactorUptimeDaysJson, 'Release')
			
		end
		ReactorUptimeWTDJson = ''
		If SRP_Json(ReactorUptimeWTDJson, 'New') then	
			If ASMPeriodAvailMinutes GT 0 then
				ASMWTDUptimePercent = SRP_Math('ROUND', (ASMPeriodUptimeMinutes / ASMPeriodAvailMinutes) * 100, 2)
			end else
				ASMWTDUptimePercent = 0
			end
			If HTRPeriodAvailMinutes GT 0 then
				HTRWTDUptimePercent = SRP_Math('ROUND', (HTRPeriodUptimeMinutes / HTRPeriodAvailMinutes) * 100, 2)
			end else
				HTRWTDUptimePercent = 0
			end
			If EPPPeriodAvailMinutes GT 0 then
				EPPWTDUptimePercent = SRP_Math('ROUND', (EPPPeriodUptimeMinutes / EPPPeriodAvailMinutes) * 100, 2)
			end else
				EPPWTDUptimePercent = 0
			end
			If ALLPeriodAvailMinutes GT 0 then
				ALLWTDUptimePercent = SRP_Math('ROUND', (ALLPeriodUptimeMinutes / ALLPeriodAvailMinutes) * 100, 2)
			end else
				ALLWTDUptimePercent = 0
			end
			SRP_Json(ReactorUptimeWTDJson, "SetValue", "ASMWtdUptimeMinutes", ASMPeriodUptimeMinutes)
			SRP_Json(ReactorUptimeWTDJson, "SetValue", "ASMWTDAvailMinutes", ASMPeriodAvailMinutes)
			SRP_Json(ReactorUptimeWTDJson, "SetValue", "ASMWTDUptimePercent", ASMWTDUptimePercent)
			SRP_Json(ReactorUptimeWTDJson, "SetValue", "HTRWTDUptimeMinutes", HTRPeriodUptimeMinutes)
			SRP_Json(ReactorUptimeWTDJson, "SetValue", "HTRWTDAvailMinutes", HTRPeriodAvailMinutes)
			SRP_Json(ReactorUptimeWTDJson, "SetValue", "HTRWTDUptimePercent", HTRWTDUptimePercent)
			SRP_Json(ReactorUptimeWTDJson, "SetValue", "EPPWTDUptimeMinutes", EPPPeriodUptimeMinutes)
			SRP_Json(ReactorUptimeWTDJson, "SetValue", "EPPWTDAvailMinutes", EPPPeriodAvailMinutes)
			SRP_Json(ReactorUptimeWTDJson, "SetValue", "EPPWTDUptimePercent", EPPWTDUptimePercent)
			SRP_Json(ReactorUptimeWTDJson, "SetValue", "ALLWTDUptimeMinutes", ALLPeriodUptimeMinutes)
			SRP_Json(ReactorUptimeWTDJson, "SetValue", "ALLWTDAvailMinutes", ALLPeriodAvailMinutes)
			SRP_Json(ReactorUptimeWTDJson, "SetValue", "ALLWTDUptimePercent", ALLWTDUptimePercent)
			SRP_Json(ReactorUptimeJson, 'Set', 'WTDMetrics', ReactorUptimeWTDJson)
			SRP_Json(ReactorUptimeWTDJson, 'Release')
		end
		// Now get the actual JSON
		ResponseJson = SRP_Json(ReactorUptimeJson, "Stringify", "STYLED")
		
		// All done with the root object
		SRP_Json(ReactorUptimeJson, "Release")
		Response = ResponseJson
	end
	
end service


Service GetDailyPerformanceDataJSON(DateOut)
	
	Response   = ''
	ReportDate = IConv(DateOut, 'D')
	ReportData = ''
	
	Query = "SELECT DAILY_PERFORMANCE_REPORT WITH DATE_OUT EQ '":DateOut:"' BY REACTOR"
	RList(Query, TARGET_ACTIVELIST$, '', '', '')
	If Not(Get_Status(ErrCode)) then
		EOF = False$
		If SRP_JSON(objJSON, 'New', 'Object') then
			If SRP_JSON(objReactArray, 'New', 'Array') then
				Loop
					ReadNext KeyID else EOF = True$
				Until EOF
					Database_Services('ActivateRecord', 'DAILY_PERFORMANCE_REPORT', KeyID)
					If SRP_JSON(objRow, 'New', 'Object') then
						
						ReactorNo    = Field(KeyID, '*', 2)
						SRP_JSON(objRow, 'SetValue', 'reactorNo', ReactorNo)
						CurrModeKeys = Xlate('REACTOR_CHILD_KEY_IDS_NG', ReactorNo, 'REACT_MODE_KEY_IDS', 'X')
						CurrModeKey  = CurrModeKeys<0, 1>
						CurrMode     = Xlate('REACT_MODE_NG', CurrModeKey, 'MODE', 'X')
						SRP_JSON(objRow, 'SetValue', 'reactorType', {REACTOR_TYPE})
						SRP_JSON(objRow, 'SetValue', 'currMode', CurrMode)
						SRP_JSON(objRow, 'SetValue', 'psn', Field(KeyID, '*', 3))
						SRP_JSON(objRow, 'SetValue', 'partNo', Field(KeyID, '*', 4))
						SRP_JSON(objRow, 'SetValue', 'upHours', {HOURS_ON_PART})
						SRP_JSON(objRow, 'SetValue', 'uptimePercent', {PERCENT_ON_PART})
						SRP_JSON(objRow, 'SetValue', 'minutesPerWafer', {MIN_PER_WAFER})
						SRP_JSON(objRow, 'SetValue', 'wafersPerDay', {WAFERS_PER_DAY})
						SRP_JSON(objRow, 'SetValue', 'oee', {OEE})
						SRP_JSON(objRow, 'SetValue', 'projOut', {PROJECTED_OUT})
						SRP_JSON(objRow, 'SetValue', 'oeeCalculation', {OEE_CALCULATION})
						SRP_JSON(objRow, 'SetValue', 'timeAdjProjOut', {TIME_ADJ_PROJECTED_OUT})
						SRP_JSON(objRow, 'SetValue', 'actualOut', {ACTUAL_OUT}, 'Number')
						SRP_JSON(objRow, 'SetValue', 'totalDelta', {TOTAL_DELTA})
						SRP_JSON(objRow, 'SetValue', 'totalDeltaAlt', {TOTAL_DELTA_ALT})
						SRP_JSON(objRow, 'SetValue', 'comments', {COMMENTS})
						SRP_JSON(objRow, 'SetValue', 'projOutAlt', {PROJECTED_OUT_ALT})
						SRP_JSON(objRow, 'SetValue', 'toolProdPecent', SRP_MATH('ROUND',Xlate('DAILY_PERFORMANCE_REPORT', @ID, DAILY_PERFORMANCE_REPORT.TOTAL_PROD_PERCENT$, 'X'), 2))
						SRP_JSON(objRow, 'SetValue', 'toolUnschedPecent', SRP_MATH('ROUND',Xlate('DAILY_PERFORMANCE_REPORT', @ID, DAILY_PERFORMANCE_REPORT.TOTAL_UNSCHED_PERCENT$, 'X'), 2))
						SRP_JSON(objRow, 'SetValue', 'toolSchedPecent', SRP_MATH('ROUND',Xlate('DAILY_PERFORMANCE_REPORT', @ID, DAILY_PERFORMANCE_REPORT.TOTAL_SCHED_PERCENT$, 'X'), 2))
						SRP_JSON(objRow, 'SetValue', 'toolProdMin', SRP_MATH('ROUND',Xlate('DAILY_PERFORMANCE_REPORT', @ID, DAILY_PERFORMANCE_REPORT.TOTAL_PROD_MIN$, 'X'), 2))
						SRP_JSON(objRow, 'SetValue', 'toolUnschedMin', SRP_MATH('ROUND',Xlate('DAILY_PERFORMANCE_REPORT', @ID, DAILY_PERFORMANCE_REPORT.TOTAL_UNSCHED_MIN$, 'X'), 2))
						SRP_JSON(objRow, 'SetValue', 'toolSchedMin', SRP_MATH('ROUND',Xlate('DAILY_PERFORMANCE_REPORT', @ID, DAILY_PERFORMANCE_REPORT.TOTAL_SCHED_MIN$, 'X'), 2))
						SRP_JSON(objReactArray, 'Add', objRow)
						SRP_JSON(objRow, 'Release')
					end
				Repeat
				SRP_JSON(objJSON, 'Set', 'performanceReport', objReactArray)
				SRP_JSON(objReactArray, 'Release')
			end
		end
		Response = SRP_JSON(objJSON, 'Stringify', 'Styled')
		SRP_JSON(objJSON, 'Release')
	end
	
end service


Service UpdateDailyPerformanceData(RepDate, RepJSON)
	
	If ( (RepDate NE '') and (RepJSON NE '') ) then
		If (SRP_JSON(objJSON, 'Parse', RepJSON) EQ '') then
			objArray   = SRP_JSON(objJSON, 'Get', 'performanceReport')
			ArrayCount = SRP_JSON(objArray, 'GetCount')
			For ArrayIndex = 1 to ArrayCount
				objRow    = SRP_JSON(objArray, 'Get', '[':ArrayIndex:']')
				ReactorNo = SRP_JSON(objRow, 'GetValue', 'reactorNo')
				PSNo      = SRP_JSON(objRow, 'GetValue', 'psn')
				PartNo    = SRP_JSON(objRow, 'GetValue', 'partNo')
				Comments  = SRP_JSON(objRow, 'GetValue', 'comments', '')
				RepKey = IConv(RepDate, 'D'):'*':ReactorNo:'*':PSNo:'*':PartNo
				RepRec = Database_Services('ReadDataRow', 'DAILY_PERFORMANCE_REPORT', RepKey)
				If Error_Services('NoError') then
					RepRec<DAILY_PERFORMANCE_REPORT.COMMENTS$> = Comments
					Database_Services('WriteDataRow', 'DAILY_PERFORMANCE_REPORT', RepKey, RepRec)
				end
				SRP_JSON(objRow, 'Release')
			Next ArrayIndex
			SRP_JSON(objArray, 'Release')
			SRP_JSON(objJSON, 'Release')
		end
	end
	
end service


// Calculate the last month's worth of performance data one day at a time.
Service CalcInProcessDailyPerformanceData()
	
	ReportDate  = Database_Services('ReadDataRow', 'APP_INFO', 'PERFORMANCE_REPORT_DATE')
	Report_Services('CalcDailyPerformanceData', ReportDate)
	ReportDate  = IConv(ReportDate, 'D')
	ReportDate -= 1
	If ReportDate LT Date() - 2 then ReportDate = Date()
	ReportDate  = OConv(ReportDate, 'D2/')
	Database_Services('WriteDataRow', 'APP_INFO', 'PERFORMANCE_REPORT_DATE', ReportDate)
	
end service


Service GetOpenTestWaferLotWIPReportJson()
	
	ErrorMessage    = ''
	JSONCollection  = ''
	TestWaferLots   = Test_Run_Services('GetTestWaferLots', True$)
	hJSONCollection = ''
	If SRP_JSON(hJSONCollection, 'New', 'Object') then
		hTWLotArray = ''
		If SRP_JSON(hTWLotArray, 'New', 'Array') then
			Abort = False$
			for each TestWaferLotId in TestWaferLots using @VM
				ThisLotJson = Lot_Services('ConvertLotRecordToJson', TestWaferLotId, '', '', False$)
				If Error_Services('NoError') then
					hTWLot = ''
					If (SRP_JSON(hTWLot, 'Parse', ThisLotJson) EQ '') then
						SRP_JSON(hTWLotArray, 'Add', hTWLot)
						SRP_JSON(hTWLot, 'Release')
					end
				end else
					Abort   = True$
				end
			Until Abort
			Next TestWaferLotId
			If Abort EQ False$ then
				SRP_JSON(hJSONCollection, 'Set', 'Lots', hTWLotArray)
			end
			SRP_JSON(hTWLotArray, 'Release')
		end else
			ErrorMessage = 'Error creating test wafer Json array.'
		end
		JSONCollection = SRP_JSON(hJSONCollection, 'Stringify', 'Styled')
		SRP_JSON(hJSONCollection, 'Release')
	end else
		ErrorMessage = 'Error creating test wafer Json Collection'
	end
	If ErrorMessage NE '' then
		Error_Services('Add', ErrorMessage)
	end
	Response = JSONCollection
	
end service


Service GenerateMaterialTrackRows()

	hSysLists = Database_Services('GetTableHandle', 'SYSLISTS')
	Lock hSysLists, ServiceKeyID then
		
		Abort     = False$
		NoMatFlag = True$
		
		LocationFilter        = ''
		LocationFilter<1, 1>  = 'SR*KTR]'
		LocationFilter<1, 2>  = '1K*PTI'
		LocationFilter<1, 3>  = 'CR*BE'
		LocationFilter<1, 4>  = 'CR*BO'
		LocationFilter<1, 5>  = 'CR*TUN'
		LocationFilter<1, 6>  = 'CR*EPR'
		LocationFilter<1, 7>  = 'CR*FE'
		LocationFilter<1, 8>  = 'CR*FEH'
		LocationFilter<1, 9>  = 'CR*FO'
		LocationFilter<1, 10> = 'CR*FOH'
		
		// All Possible Report Columns
		AllRptColumns        = ''
		AllRptColumns<1, 1>  = 'React No'
		AllRptColumns<1, 2>  = 'React Type'
		AllRptColumns<1, 3>  = 'WO No'
		AllRptColumns<1, 4>  = 'SAP Prod No'
		AllRptColumns<1, 5>  = 'Sub Part No'
		AllRptColumns<1, 6>  = 'Epi Part No'
		AllRptColumns<1, 7>  = 'WO Qty'
		AllRptColumns<1, 8>  = 'RX Qty'
		AllRptColumns<1, 9>  = 'UnRel Qty'
		AllRptColumns<1, 10> = 'Kit Location'
		AllRptColumns<1, 11> = 'Kit Qty'
		AllRptColumns<1, 12> = '+/-'
		AllRptColumns<1, 13> = 'Kit RO'
		AllRptColumns<1, 14> = 'PTI RO'
		AllRptColumns<1, 15> = 'Load'
		AllRptColumns<1, 16> = 'Comments'
		
		RptColumns = AllRptColumns
		
		ErrorTitle  =  'Error in service ':Service
		ErrCode		= ''
		ErrorMsg	= ''
		
		SchedDetTable   = ''
		SchedDetDict    = ''
		
		OPEN 'SCHED_DET_NG' TO SchedDetTable then
			OPEN 'DICT.SCHED_DET_NG' TO SchedDetDict then
				Today   = Datetime()
				
				ReactList                  = ''
				RemainingReactorDemandList = ''
				WOList                     = ''
				SchedDetKeys               = ''
				PrevReactNo                = ''
				PrevWO                     = ''
				
				SelectSent  = 'SELECT SCHED_DET_NG WITH STOP_DTM GE "':OCONV(Today, 'DT'):'" BY REACT_NO BY START_DTM'
				
				RList(SelectSent,TARGET_ACTIVELIST$,'','','')
				IF Get_Status(errCode) THEN
					ErrMsg(errCode)
					Unlock hSysLists, ServiceKeyID else Null
					RETURN
				END
				
				AsmDemand = Database_Services('ReadDataRow', 'APP_INFO', 'MATERIAL_REPORT*ASM_DEMAND')
				HtrDemand = Database_Services('ReadDataRow', 'APP_INFO', 'MATERIAL_REPORT*HTR_DEMAND')
				EppDemand = Database_Services('ReadDataRow', 'APP_INFO', 'MATERIAL_REPORT*EPP_DEMAND')
				
				Done = 0
				CurrSchedDetKey  = ''
				LOOP
					PrevSchedDetKey = CurrSchedDetKey
					READNEXT CurrSchedDetKey ELSE Done = 1
				UNTIL Done
					READ SchedDetRec FROM SchedDetTable,CurrSchedDetKey THEN
						ReactNo = SchedDetRec<SCHED_DET_NG.REACT_NO$>
						WONo    = SchedDetRec<SCHED_DET_NG.WO_NO$>
						If ReactNo NE '' then
							LOCATE ReactNo IN ReactList BY 'AR' USING @FM  SETTING Pos ELSE
							    ReactType = Database_Services('ReadDataColumn', 'REACTOR', ReactNo, REACTOR_REACT_TYPE$)
								ReactList = Insert(ReactList,Pos,0,0,ReactNo)
								Begin Case
								    Case IndexC(ReactType, 'ASM', 1)
								        ReactTypeDemand = AsmDemand
								    Case IndexC(ReactType, 'HTR', 1)
								        ReactTypeDemand = HtrDemand
								    Case IndexC(ReactType, 'EPP', 1)
								        ReactTypeDemand = EppDemand
								    Case Otherwise$
								        ReactTypeDemand = 0
								End Case
								RemainingReactorDemandList = Insert(RemainingReactorDemandList, Pos, 0, 0, ReactTypeDemand)
							END
						END
						If WONo NE '' then
							LOCATE WONo IN WOList<Pos> USING @VM SETTING WPos ELSE	
								WOList       = Insert(WOList,Pos,-1,0,WONo)
								SchedDetKeys = Insert(SchedDetKeys,-1,0,0,CurrSchedDetKey)
							END
						END
					END
					
				REPEAT
				
				CALL Make.List(0, SchedDetKeys, SchedDetTable, SchedDetDict)
				
				DateRange = 'Effective ':OCONV(Date(),'D4')
				
				RowIndex      = 0
				AllReportData = ''
				RecCount      = 0
				FirstPass     = 1
				LastRecord    = 0
				FirstLine     = 1
				fontSpacing   = 100
				
				* Zero Accumulators For Each Break
				
				Prev.ReactNo       = ''
				Last.ReactNo.Break = 1
				
				CurrSchedDetRec = ''
				
				Loop
					
					ReactNo.Break = 0
					
					READNEXT CurrSchedDetKey, Which.Value ELSE
						LastRecord    = 1
						ReactNo.Break = 1
						ReactNo       = Prev.ReactNo
					END
					
					S.ATID = CurrSchedDetKey
					Abort  = (FirstPass and LastRecord)
					If Not(Abort) then
						
						If Not(LastRecord) then 
						
							READO CurrSchedDetRec FROM SchedDetTable,CurrSchedDetKey then
								
								// GOTO ReadRecord
								RecCount += 1
								
								* Calculate Value(s) For Column(s)
								
								S.StartDtm     = CurrSchedDetRec<SCHED_DET_NG.START_DTM$>
								S.ATID         = CurrSchedDetKey
								I.ATID         = S.ATID
								S.ReactNo      = CurrSchedDetRec<SCHED_DET_NG.REACT_NO$>
								I.ReactNo      = S.ReactNo
								S.WoNo         = CurrSchedDetRec<SCHED_DET_NG.WO_NO$>
								I.WoNo         = S.WoNo
								S.SubPartNo    = XLATE('WO_LOG', S.WoNo, 'ORD_SUB_PART_NO', 'X')
								I.SubPartNo    = S.SubPartNo
								S.EpiPartNo    = XLATE('WO_LOG', S.WoNo, WO_LOG_EPI_PART_NO$, 'X')
								I.EpiPartNo    = S.EpiPartNo
								S.WO_Qty       = Xlate('WO_LOG', S.WoNo, WO_LOG_WO_QTY$, 'X')
								I.WO_Qty       = S.WO_Qty
								S.WO_RX_Qty    = Xlate('WO_LOG', S.WoNo, 'RX_QTY', 'X')
								I.WO_RX_Qty    = S.WO_RX_Qty
								S.WO_UnRel_QTY = Xlate('WO_LOG', S.WoNo, 'UNREL_QTY', 'X')
								I.WO_UnRel_QTY = S.WO_UnRel_QTY
								S.REACT_TYPE   = Xlate('WO_LOG', S.WoNo, 'REACT_TYPE', 'X')
								S.ProdOrdNo    = Xlate('WO_LOG', S.WoNo, 'PROD_ORD_NO', 'X')
								
								Locate 'SR*KTR]' in LocationFilter using @VM setting vPos then
									KitData = Location_Services('GetLocationCassInfo', S.WONo, 'SR*KTR]')
									SWAP CRLF$ WITH @VM IN KitData
								end else
									KitData = ''
								end
								
								KitCassList = KitData<3>
								NewKitData  = ''
								
								Swap ',' with @VM in KitData
								
								// Filter out cassettes on hold
								NewKitData<1> = KitData<1>
								Swap @VM with ',' in KitCassList
								For each Cass in KitCassList using ',' setting cPos
									Key    = S.WONo:'*':Cass
									OnHold = Xlate('WO_MAT', Key, 'HOLD', 'X')
									If OnHold NE True$ then
										NewKitData<3, -1> = KitData<3, cPos>
									end
								Next Cass
								
								LocInfo    = ''
								LocInfo<1> = NewKitData<1>
								LocInfo<2> = DCount(NewKitData<3>, @VM)
								LocInfo<3> = NewKitData<3>
								Database_Services('WriteDataRow', 'WO_LOC', S.WONo:'*':'SR*KTR]', LocInfo)
								
								NewKitData<2> = DCount(NewKitData<3>, @VM) ; // Update cassette count
								KitData       = NewKitData
								
								Swap @VM with ',' in KitData
								
								S.KitLocation = KitData<1>
								I.KitLocation = S.KitLocation
								
								S.KitCassCnt	= COUNT(KitData<3>,',') + (KitData<3> NE '')
								I.KitCassCnt	= S.KitCassCnt
								
								KitCassList = KitData<3>
								S.KitQty    = 0
								For each CassNo in KitCassList using ','
									S.KitQty += Xlate('WO_MAT', S.WONo:'*':CassNo, 'WAFER_QTY', 'X')
								Next CassNo
								I.KitQty = S.KitQty
								
								Locate '1K*PTI' in LocationFilter using @VM setting vPos then
									PTIData = Location_Services('GetLocationCassInfo', S.WONo, '1K*PTI')
									SWAP CRLF$ WITH @VM IN PTIData    
								end else
									PTIData = ''
								end
								PTICassList = PTIData<3>
								NewPTIData  = ''
								
								Swap ',' with @VM in PTIData 
								// Filter out cassettes on hold
								NewPTIData<1> = PTIData<1>
								Swap @VM with ',' in PTICassList
								For each Cass in PTICassList using ',' setting cPos
									Key    = S.WONo:'*':Cass
									OnHold = Xlate('WO_MAT', Key, 'HOLD', 'X')
									If OnHold NE True$ then
										NewPTIData<3, -1> = PTIData<3, cPos>
									end
								Next Cass
								
								LocInfo    = ''
								LocInfo<1> = '1K*PTI'
								LocInfo<2> = DCount(NewPTIData<3>, @VM)
								LocInfo<3> = NewPTIData<3>
								Database_Services('WriteDataRow', 'WO_LOC', S.WONo:'*':'1K*PTI', LocInfo)
								
								NewPTIData<2> = DCount(NewPTIData<3>, @VM) ; // Update cassette count
								PTIData       = NewPTIData
								
								Swap @VM with ',' in PTIData
								PTICassList = PTIData<3>
								
								CRLocs        = ''
								CRLocs<0, -1> = 'CR*BE'
								CRLocs<0, -1> = 'CR*BO'
								CRLocs<0, -1> = 'CR*TUN'
								CRLocs<0, -1> = 'CR*EPR'
								CRLocs<0, -1> = 'CR*FE'
								CRLocs<0, -1> = 'CR*FEH'
								CRLocs<0, -1> = 'CR*FO'
								CRLocs<0, -1> = 'CR*FOH'
								
								LoadedData     = ''
								LocQueryList   = ''
								For each Loc in CRLocs using @VM setting crPos
									Locate Loc in LocationFilter using @VM setting vPos then
										LocQueryList<0, -1> = Loc    
									end
								Next Loc
								LoadedCassList = ''
								ReactType      = Xlate('WO_LOG', S.WONo, 'REACT_TYPE', 'X')
								
								If ReactType NE 'EPP' then
									If LocQueryList NE '' then
										For each CRLoc in LocQueryList using @VM
											CRLocCassList = ''
											LoadedData    = Location_Services('GetLocationCassInfo', S.WONo, CRLoc)
											Swap CRLF$ with @VM in LoadedData
											CassList      = LoadedData<3>
											Swap @VM with ',' in CassList
											For each CassNo in CassList using ','
												If ( (S.WONo NE '') and (CassNo NE '') ) then
													WOMatKey      = S.WONo:'*':CassNo
													UnloadSigComp = Signature_Services('CheckSignature', WOMatKey, 'UNLOAD')
													OnHold        = Xlate('WO_MAT', WOMatKey, 'HOLD', 'X')
													CurrStatus    = Xlate('WO_MAT', WOMatKey, 'CURR_STATUS', 'X')
													If Not(UnloadSigComp or OnHold or (CurrStatus EQ 'REJ') ) then
														CRLocCassList<0, -1>  = CassNo
														Locate CassNo in LoadedCassList using @VM setting vPos else
															LoadedCassList<0, -1> = CassNo
														end
													end
												end
											Next CassNo
											LocInfo    = ''
											LocInfo<3> = CRLocCassList
											Database_Services('WriteDataRow', 'WO_LOC', S.WONo:'*':CRLoc, LocInfo)
										Next CRLoc
									end
								end
								
								* TEST FOR CONTROL BREAK(S)
								
								IF (S.ReactNo NE Prev.ReactNo) OR ReactNo.Break THEN
									ReactNo			= Prev.ReactNo
									Prev.ReactNo	= S.ReactNo
									ReactNo.Break  += 1
								END
								
								IF FirstPass THEN FirstPass = False$
									
								* Do Conversion If Any
								
								IF S.REACTNO NE "" 		THEN S.REACTNO		= OCONV(S.REACTNO,"MD0,")
								IF S.WONO NE "" 		THEN S.WONO			= OCONV(S.WONO,"MD0")
								IF S.WO_QTY NE "" 		THEN S.WO_QTY		= OCONV(S.WO_QTY,"MD0,")
								IF S.WO_RX_QTY NE ""	THEN S.WO_RX_QTY	= OCONV(S.WO_RX_QTY,"MD0,")
								IF S.WO_UNREL_QTY NE "" THEN S.WO_UNREL_QTY	= OCONV(S.WO_UNREL_QTY,"MD0,")
								
								* PRINT DETAIL LINE
								
								COLDATA        = ''
								RowNumber      = 0
								
								/*  ascending sort  */
								Convert @VM to ',' in KitCassList
								Convert @VM to ',' in PTICassList
								Convert @VM to ',' in LoadedCassList
								KitCassList    = SRP_Array('SortSimpleList', KitCassList, 'AscendingNumbers', ',')
								PTICassList    = SRP_Array('SortSimpleList', PTICassList, 'AscendingNumbers', ',')
								LoadedCassList = SRP_Array('SortSimpleList', LoadedCassList, 'AscendingNumbers', ',')
								
                                Locate S.ReactNo in ReactList using @FM setting rPos then
                                    ThisReactorRemainingDemand = RemainingReactorDemandList<rPos>
                                    PTICassCount                = DCount(PTICassList, ',')
                                    ThisReactorRemainingDemand -= PTICassCount
                                    If ThisReactorRemainingDemand LT 0 then ThisReactorRemainingDemand = 0
                                    RemainingReactorDemandList<rPos>  = ThisReactorRemainingDemand
                                    If ThisReactorRemainingDemand GT 0 then
                                        KitDemand                         = Field(KitCassList, ',', 1, ThisReactorRemainingDemand)
                                        ThisReactorRemainingDemand       -= DCount(KitDemand, ',')
                                        RemainingReactorDemandList<rPos>  = ThisReactorRemainingDemand
                                    end else
                                        KitDemand = ''
                                    end
                                end else
                                    KitDemand = ''
                                end
								
								RowIndex                    += 1
								Swap '.1' with '' in S.ProdOrdNo
								
								AllReportData<RowIndex, MATERIAL_REPORT.REACT_NO$>     = S.ReactNo
								AllReportData<RowIndex, MATERIAL_REPORT.REACT_TYPE$>   = S.React_Type
								AllReportData<RowIndex, MATERIAL_REPORT.WO_NO$>        = S.WONo
								AllReportData<RowIndex, MATERIAL_REPORT.PROD_ORD_NO$>  = S.ProdOrdNo
								AllReportData<RowIndex, MATERIAL_REPORT.SUB_PART_NO$>  = S.SubPartNo
								AllReportData<RowIndex, MATERIAL_REPORT.EPI_PART_NO$>  = S.EpiPartNo
								AllReportData<RowIndex, MATERIAL_REPORT.WO_QTY$>       = S.WO_Qty
								AllReportData<RowIndex, MATERIAL_REPORT.WO_RX_QTY$>    = S.WO_RX_Qty
								AllReportData<RowIndex, MATERIAL_REPORT.WO_UNREL_QTY$> = S.WO_UnRel_Qty
								AllReportData<RowIndex, MATERIAL_REPORT.KIT_LOCATION$> = S.KitLocation
								AllReportData<RowIndex, MATERIAL_REPORT.KIT_QTY$>      = S.KitQty
								AllReportData<RowIndex, MATERIAL_REPORT.KIT_RO$>       = KitCassList
								AllReportData<RowIndex, MATERIAL_REPORT.KIT_DEMAND$>   = KitDemand
								AllReportData<RowIndex, MATERIAL_REPORT.PTI_RO$>       = PTICassList
								AllReportData<RowIndex, MATERIAL_REPORT.LOAD_RO$>      = LoadedCassList 
								AllReportData<RowIndex, MATERIAL_REPORT.START_DTM$>    = S.StartDtm
						
							end
						end else
							// Last record -> write data
							
							Database_Services('WriteDataRow', 'SYSLISTS', 'MAT_REPORT', AllReportData)
							
							If AllReportData NE '' then
								KeyList = ''
								For each Row in AllReportData using @FM setting fPos
									Key            = Row<0, 1> : '*': Row<0, 3> ; // Key = ReactNo * WONo
									KeyList<0, -1> = Key
									Convert @VM to @FM in Row
									If RowExists('MATERIAL_REPORT', Key) then
										// Update it
										Rec = Database_Services('ReadDataRow', 'MATERIAL_REPORT', Key)
										If Error_Services('NoError') then
											// Preserve user input fields
											Row<MATERIAL_REPORT.COMMENTS$>   = Rec<MATERIAL_REPORT.COMMENTS$>
											Database_Services('WriteDataRow', 'MATERIAL_REPORT', Key, Row)
										end
									end else
										// New record -> Just write it
										Database_Services('WriteDataRow', 'MATERIAL_REPORT', Key, Row)
									end
								Next Row
								
								// Remove rows no longer current
								Open "MATERIAL_REPORT" To hTable then
									Select hTable
									EOF = False$
									Loop
										ReadNext Key else EOF = True$
									Until EOF
										If Not(InList(KeyList, Key, @VM)) then
											Database_Services('DeleteDataRow', 'MATERIAL_REPORT', Key, True$, False$)
										end
									Repeat
								End else
									// Open table failed
								end
							end
						end
					end else
						ErrorMsg = 'No records selected!'
					end
				Until (LastRecord or Abort)
				Repeat 
			end else
				ErrorMsg = 'Unable to open "SCHED_DET_NG" table.'
			end
		end else
			ErrorMsg  =  'Unable to open "SCHED_DET_NG" table.'
		end
		Unlock hSysLists, ServiceKeyID else Null
	end	
	
end service


Service UpdateMaterialTrackData(MaterialTrackJSON)
	
	If MaterialTrackJSON NE '' then
		If (SRP_JSON(objJSON, 'Parse', MaterialTrackJSON) EQ '') then
			objArray   = SRP_JSON(objJSON, 'Get', 'MaterialReport')
			ArrayCount = SRP_JSON(objArray, 'GetCount')
			For ArrayIndex = 1 to ArrayCount
				objRow    = SRP_JSON(objArray, 'Get', '[':ArrayIndex:']')
				KeyId     = SRP_JSON(objRow, 'GetValue', 'KeyId')
				Comments  = SRP_JSON(objRow, 'GetValue', 'Comments', '')
				HaveLock  = Database_Services('GetKeyIDLock', 'MATERIAL_REPORT', KeyId, True$)
				If HaveLock then 
					MatRepRec = Database_Services('ReadDataRow', 'MATERIAL_REPORT', KeyId)
					If Error_Services('NoError') then
						MatRepRec<MATERIAL_REPORT.COMMENTS$>   = Comments
						Database_Services('WriteDataRow', 'MATERIAL_REPORT', KeyId, MatRepRec, True$, False$, False$)
					end
				end
				SRP_JSON(objRow, 'Release')
			Next ArrayIndex
			SRP_JSON(objArray, 'Release')
			SRP_JSON(objJSON, 'Release')
		end	else
			Error_Services('Add', 'Error in ':Service:' service. Error parsing MaterialTrackJSON.')
		end
	end else
		Error_Services('Add', 'Error in ':Service:' service. Empty MaterialTrackJSON passed into service.')
	end
	
end service


Service GetOpenNCRReportJson()
	
    ErrorMessage      = ''
    OpenNCRReportJson = ''
    OpenNCRKeyList    = NCR_Services('GetOpenNCRKeys')
	objJson           = ''
	If SRP_Json(objJson, 'New') then
	    objOpenNCRJsonArray = ''
	    If SRP_Json(objOpenNCRJsonArray, 'New', 'Array') then
	        for each NCRKey in OpenNCRKeyList using @VM
	            ThisNCRJsonString = NCR_Services('ConvertRecordToJson', NCRKey)
	            ThisNCRJsonObj    = ''
	            ParseResult       = SRP_Json(ThisNCRJsonObj, 'Parse', ThisNCRJsonString)
	            If ParseResult EQ '' then
	                SRP_Json(objOpenNCRJsonArray, 'Add', ThisNCRJsonObj)
	                SRP_Json(ThisNCRJsonObj, 'Release')
	            end else
	                SRP_Json(ThisNCRJsonObj, 'Release')
	            end
	        Next NCRKey
	        SRP_Json(objJson, 'Set', 'OpenNCRReport', objOpenNCRJsonArray)
	        SRP_Json(objOpenNCRJsonArray, 'Release')
	    end else
	       ErrorMessage = 'Error creating Open NCR JSON Array.' 
	    end
	    OpenNCRReportJson = SRP_JSON(objJSON, 'Stringify', 'Styled')
        SRP_JSON(objJSON, 'Release')
	end else
	    ErrorMessage = 'Error creating root JSON object.'
	end
	If ErrorMessage NE '' then
	    Error_Services('Add', ErrorMessage)
	end
	Response = OpenNCRReportJson
end service


/* * * * * *
Called from Reporting Services

* * * * * */
Service GetSLLReportJson(FromDt, ToDt)
    
    FromDt = IConv(FromDt, 'D')
    ToDt = IConv(ToDt, 'D')
    ErrorMessage = ''
    SLLReportJson = ''
    objSLLReportJson = ''
    If FromDt NE '' OR ToDt NE '' then
        If SRP_Json(objSLLReportJson, 'New', 'Array') then
            For StartDt = FromDt to ToDt
                ThisDate = Date_Services('ConvertDateTimeToISO8601', StartDt)
                ThisDtASMDownLL = Reactor_Services('GetReactorsWithDownLLByDtm', StartDt + 1, 'ASM')
                ThisDtASMPlusDownLL = Reactor_Services('GetReactorsWithDownLLByDtm', StartDt + 1, 'ASM+')
                ThisDtHTRDownLL = Reactor_Services('GetReactorsWithDownLLByDtm', StartDt + 1, 'HTR')
                ThisDtASMDownLLCount = DCount(ThisDtASMDownLL, @VM) + DCount(ThisDtASMPlusDownLL, @VM)
                ThisDtHTRDownLLCount = DCount(ThisDtHTRDownLL, @VM)
                objSLLDay = ''
                If SRP_Json(objSLLDay, 'New', 'Object') then
                    SRP_Json(objSLLDay, 'SetValue', 'Date', ThisDate)
                    SRP_Json(objSLLDay, 'SetValue', 'ASM', ThisDtASMDownLLCount)
                    SRP_Json(objSLLDay, 'SetValue', 'HTR', ThisDtHTRDownLLCount)
                    SRP_Json(objSLLReportJson, 'Add', objSLLDay)
                    SRP_Json(objSLLDay, 'Release')
                end
            Next StartDt
            SLLReportJson = SRP_Json(objSLLReportJson, 'Stringify', 'Fast')
            SRP_Json(objSLLReportJson, 'Release')
        end else
            ErrorMessage = 'Error creating the root JSON for the report.'
        end
    end else
        ErrorMessage = 'From Date or To Date was null.'
    end
    
    If ErrorMessage NE '' then
        Error_Services('Add', ErrorMessage)
    end
    
    Response = SLLReportJson
    
end service

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

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


OipiPrintError:
	
	ErrMsg(ErrorTitle:@SVM:ErrorMsg)
	ErrMsg(ErrorTitle:@SVM:'Set_Printer returned errorcode ':stat)
	stat = Set_Printer('TERM',1)		
	
return

