Function Work_Order_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        :   Work_Order_Services

    Description :   Handler program for all module related services.

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

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

    Metadata    :

    History     :   (Date, Initials, Notes)
        06/16/17    dmb     Original programmer. - [EPIOI-8]
        11/23/20    djs     Added CreateOutbound service.

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

#pragma precomp SRP_PreCompiler

$insert APP_INSERTS
$insert SERVICE_SETUP
$insert WO_LOG_EQUATES
$insert EPI_PART_EQUATES
$insert WO_MAT_EQUATES
$insert WO_STEP_EQUATES
$insert WO_SCHEDULE_EQUATES
$insert WO_SCHEDULE_NG_EQUATES
$insert RDS_EQUATES
$insert PROD_SPEC_EQUATES
$insert PRS_LAYER_EQU
$insert PROD_VER_EQUATES
$insert WO_MAT_WFR_EQUATES
$insert MSG_EQUATES
$insert POPUP_EQUATES
$insert LOGICAL
$insert RLIST_EQUATES
$insert COMPANY_EQUATES
$Insert NOTIFICATION_EQUATES

Declare subroutine  Error_Services, Work_Order_Services, Memory_Services, RList, Database_Services, SRP_JSON
Declare subroutine  Btree.Extract, Set_Status, obj_WO_Log, obj_Notes, Print_Wo_Mat_In_Labels, Print_Wo_Mat_Out_Labels
Declare subroutine  Print_Wmi_Labels, Print_Wmo_Labels, ErrMsg, Print_Cass_Labels, Logging_Services
Declare function    SRP_Array, Work_Order_Services, Memory_Services, Database_Services, SRP_Sort_Array, SRP_JSON
Declare function    Company_Services, obj_Prod_Spec, Schedule_Services, Datetime, obj_WO_Log, obj_WO_Step, Memberof
Declare function    Environment_Services, Logging_Services

LogPath       = Environment_Services('GetApplicationRootPath') : '\LogFiles\WO_LOG'
LogDate       = Oconv(Date(), 'D4/')
LogTime       = Oconv(Time(), 'MTS')
LogFileName   = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' Release Log.csv'
Headers       = 'Logging DTM' : @FM : 'User' : @FM : 'Notes'
objReleaseLog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$,',', Headers, '', False$, False$)

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

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

Return Response else ''

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

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

//----------------------------------------------------------------------------------------------------------------------
// GetUnscheduledWorkOrders
//
// Returns an array of unscheduled work order details based on the arguments provided.
//----------------------------------------------------------------------------------------------------------------------
Service GetUnscheduledWorkOrders(ReactorType, SusceptorSize, ReactorNo)

    WorkOrders          = ''
    // Select WO_LOG rows that do not have a closed date.
    OpenWOLogKeyIDs     = Database_Services('SearchIndex', 'WO_LOG', 'CLOSE_DATE', '=', True$)
    // Check each WO_LOG Key ID against the WO_SCHEDULE table. If the row does not exist or if the row does not have any
    // SCED_DET Key IDs, exists then it is not scheduled an should be included.
    WOLogKeyIDs     = Str(\00\, 100000)
    If OpenWOLogKeyIDs NE '' then
        hWOSchedule     = Database_Services('GetTableHandle', 'WO_SCHEDULE')
        If Error_Services('NoError') then
            cPos    = 1
            For Each WOLogKeyID in OpenWOLogKeyIDs using @FM
                Read WOScheduleRow from hWOSchedule, WOLogKeyID then
                    If WOScheduleRow<WO_SCHEDULE_SCHED_DET_KEY_IDS$> EQ '' then
                        WOLogKeyIDs[cPos, Len(WOLogKeyID) + 1]  = WOLogKeyID : @FM
                        cPos   += Len(WOLogKeyID) + 1
                    end
                end else
                    WOLogKeyIDs[cPos, Len(WOLogKeyID) + 1]  = WOLogKeyID : @FM
                    cPos   += Len(WOLogKeyID) + 1
                end
            Next WOLogKeyID
        end
    end
    Convert \00\ to '' in WOLogKeyIDs
    WOLogKeyIDs[-1, 1]  = ''

    If Error_Services('NoError') then
        For Each WOLogKeyID in WOLogKeyIDs using @FM setting fPos
            WOLogRow        = Database_Services('ReadDataRow', 'WO_LOG', WOLogKeyID)
            EpiPartNo       = WOLogRow<WO_LOG_EPI_PART_NO$>
            ProdVerNo       = WOLogRow<WO_LOG_PROD_VER_NO$>
            ProdVerRow      = Database_Services('ReadDataRow', 'PROD_VER', ProdVerNo)
            PSN             = ProdVerRow<PROD_VER_PROC_STEP_PSN$>
            ProdSpecRow     = Database_Services('ReadDataRow', 'PROD_SPEC', PSN)
            BlockedReactors = ProdSpecRow<PROD_SPEC_BLOCKED_REACTS$>
            PSNBlock        = False$
            If EpiPartNo NE '' then
                AcceptWorkOrder = True$ ; // Assume this work order is acceptable for now.
                ReactType       = WOLogRow<WO_LOG_REACT_TYPE$>
                // The following two lines of code are temporary but necessary to allows work orders
                // for ASM to be scheduled on ASM+ and vice-versa.
                If ReactType _EQC 'ASM+' then ReactType = 'ASM'
                If ReactorType _EQC 'ASM+' then ReactorType = 'ASM'
                If (ReactorType NE '') AND (ReactType NE ReactorType) then
                    // The reactor type for this work order does not match the required reactor type.
                    AcceptWorkOrder = False$
                end
                If AcceptWorkOrder AND SusceptorSize NE '' then
                    EpiPart         = Database_Services('ReadDataRow', 'EPI_PART', EpiPartNo)
                    SuscSize        = EpiPart<EPI_PART_SUB_WAFER_SIZE$>
                    If SuscSize NE SusceptorSize then
                        // The susceptor size for this work order does not match the required susceptor size.
                        AcceptWorkOrder = False$
                    end
                end
                If (BlockedReactors NE '') AND (ReactorNo NE '') then
                    // This work order uses a part spec that is blocked on the current reactor.
                    Locate ReactorNo in BlockedReactors using @VM setting vPos then PSNBlock = True$
                end
                If AcceptWorkOrder then
                    CustNo          = WOLogRow<WO_LOG_CUST_NO$>
                    CompanyRow      = Database_Services('ReadDataRow', 'COMPANY', CustNo)
                    CustName        = CompanyRow<42>
                    Swap True$ with 'Yes' in PSNBlock
                    Swap False$ with '' in PSNBlock
                    WorkOrders     := WOLogKeyID : @VM : EpiPartNo : @VM : CustName : @VM : ReactType : @VM : PSNBlock : @FM
                end
            end
        Next WOLogKeyID

        WorkOrders[-1, 1]   = ''
        WorkOrders          = SRP_Array('SortRows', WorkOrders, 'AR1' : @FM : 'AR2', 'LIST', @FM, @VM)
    end

    Response    = WorkOrders

end service


//----------------------------------------------------------------------------------------------------------------------
// GetWorkOrderLogDetail
//
// Returns the WO_LOG row for the indicated Key ID.
//----------------------------------------------------------------------------------------------------------------------
Service GetWorkOrderLogDetail(WorkOrderLogID)

    WorkOrderDetail = ''

    TableName       = 'WO_LOG'
    If WorkOrderLogID NE '' then
        WorkOrderDetail = Database_Services('ReadDataRow', Tablename, WorkOrderLogID)
    end else
        Error_Services('Add', 'WorkOrderLogID argument was missing from the ' : Service : ' service.')
    end

    Response    = WorkOrderDetail

end service


//----------------------------------------------------------------------------------------------------------------------
// GetWorkOrder
//
// Returns a JSON formatted object of information related to the indicated work order number. The ExtendedData argument
// determines if extended data related to wafers, product versions, and recipe should be included. The default is false.
//----------------------------------------------------------------------------------------------------------------------
Service GetWorkOrder(WorkOrderNo, ExtendedData)
SRP_Stopwatch('Start', Service)
    If ExtendedData NE True$ then ExtendedData = False$

    ServiceKeyID   := '*' : WorkOrderNo
    WorkOrder         = ''

    If Memory_Services('IsValueCurrent', ServiceKeyID, 15, True$) then
        WorkOrder     = Memory_Services('GetValue', ServiceKeyID)
    end else
        If WorkOrderNo NE '' then
            WorkOrderRow = Database_Services('ReadDataRow', 'WO_LOG', WorkOrderNo, True$, 15)
            objWorkOrder = ''
            If SRP_JSON(objWorkOrder, 'NEW', 'OBJECT') then
                SRP_JSON(objWorkOrder, 'SETVALUE', 'WorkOrderNumber', WorkOrderNo, 'STRING')
                SRP_JSON(objWorkOrder, 'SETVALUE', 'EpiPartNumber', WorkOrderRow<WO_LOG_EPI_PART_NO$>, 'STRING')
                SRP_JSON(objWorkOrder, 'SETVALUE', 'ReactorType', WorkOrderRow<WO_LOG_REACT_TYPE$>, 'STRING')
                ProdVerNo   = WorkOrderRow<WO_LOG_PROD_VER_NO$>
                SRP_JSON(objWorkOrder, 'SETVALUE', 'ProdVerNumber', ProdVerNo, 'STRING')
                TotalWafers = WorkOrderRow<WO_LOG_WO_QTY$>
                SRP_JSON(objWorkOrder, 'SETVALUE', 'TotalWafers', TotalWafers, 'STRING')
                SRP_JSON(objWorkOrder, 'SETVALUE', 'Closed', Oconv(WorkOrderRow<WO_LOG_CLOSE_DATE$> NE '', 'BYes,No'), 'STRING')
                SRP_JSON(objWorkOrder, 'SETVALUE', 'ClosedDate', Oconv(WorkOrderRow<WO_LOG_CLOSE_DATE$>, 'D4/'), 'STRING')
                SRP_JSON(objWorkOrder, 'SETVALUE', 'HotLot', Oconv(WorkOrderRow<WO_LOG_HOT_FLAG$> NE '', 'BYes,No'), 'STRING')
                CustomerNo  = WorkOrderRow<WO_LOG_CUST_NO$>
                Company     = Company_Services('GetCompany', CustomerNo)
                objCompany  = ''
                If SRP_JSON(objCompany, 'PARSE', Company) EQ '' then
                    SRP_JSON(objWorkOrder, 'SET', 'Company', objCompany)
                    SRP_JSON(objCompany, 'RELEASE')
                end
                If ExtendedData EQ True$ then
                    WafersRemaining   = Work_Order_Services('GetRemainingWafers', WorkOrderNo)
                    SRP_JSON(objWorkOrder, 'SETVALUE', 'WafersRemaining', WafersRemaining, 'STRING')
                    If TotalWafers GT 0 then
                        PercentComplete = (TotalWafers - WafersRemaining) / TotalWafers * 100
                    end else
                        PercentComplete = 0
                    end
                    PercentComplete = Oconv(Iconv(PercentComplete, 'MD2'), 'MD2%S')
                    SRP_JSON(objWorkOrder, 'SETVALUE', 'PercentComplete', PercentComplete, 'STRING')
                    ProdVerRow  = Database_Services('ReadDataRow', 'PROD_VER', ProdVerNo, True$, 15)
                    PSN         = ProdVerRow<PROD_VER_PROC_STEP_PSN$>
                    SRP_JSON(objWorkOrder, 'SETVALUE', 'PSN', PSN, 'STRING')
                    LayerSpecs  = obj_Prod_Spec('GetLayerProp', PSN : @RM : @RM : 1)    ; // Returns specs for all layers in internal format.
                    LayerSpec 	= Field(LayerSpecs, @RM, 1)				                ; // Take the first Layer
                    LayerSet	= Field(LayerSpec, @FM, 1)				                ; // Not used here but shown for clarity
                    LayerSpec	= Field(LayerSpec, @FM, 2, 99)				            ; // LayerSpec without the LayerSet 
                    Recipe      = LayerSpec<PRS_LAYER_RECIPE$>
                    SRP_JSON(objWorkOrder, 'SETVALUE', 'Recipe', Recipe, 'STRING')
                end
                WorkOrder       = SRP_JSON(objWorkOrder, 'STRINGIFY', 'FAST')
                Memory_Services('SetValue', ServiceKeyID, WorkOrder)
                SRP_JSON(objWorkOrder, 'RELEASE')
            end else
                Error_Services('Add', 'Error creating objWorkOrder in the ' : Service : ' service.')
            end
        end else
            Error_Services('Add', 'WorkOrder argument was missing from the ' : Service : ' service.')
        end
    end

    Response    = WorkOrder
SRP_Stopwatch('Stop', Service)
end service


//----------------------------------------------------------------------------------------------------------------------
// GetRemainingWafers
//
// Returns the number of remaining wafers needed to be scheduled for the indicated work order.
//----------------------------------------------------------------------------------------------------------------------
Service GetRemainingWafers(WorkOrderNo)

    RemainingWafers = ''

    If WorkOrderNo NE '' then
        WOLogRow    = Database_Services('ReadDataRow', 'WO_LOG', WorkOrderNo)

        If Error_Services('NoError') then
            // Get the total number of unreleased work orders.
            TotalWafers     = WOLogRow<WO_LOG_WO_QTY$>
            TotalReleased   = 0
            WOMatKeys       = WOLogRow<WO_LOG_WO_MAT_KEY$>
            For Each WOMatKey in WOMatKeys using @VM
                WOMatRec        = Database_Services('ReadDataRow', 'WO_MAT', WOMatKey)
                TotalReleased  += WOMatRec<WO_MAT_WAFER_QTY$>
            Next WOMatKey
            TotalUnreleased = TotalWafers - TotalReleased

            WOStepKeys          = WOLogRow<WO_LOG_WO_STEP_KEY$>
            TotalUnprocessed    = 0
            If WOStepKeys NE '' then
                ReactorType = WOLogRow<WO_LOG_REACT_TYPE$>
                For Each WOStepKey in WOStepKeys using @VM
                    WOStepRec   = Database_Services('ReadDataRow', 'WO_STEP', WOStepKey)
                    If Error_Services('NoError') then
                        // Get the total released but unprocessed.
                        If ReactorType EQ 'EPP' then
                            // EpiPro work orders have their own way of calculating.
                            StatusCodes         = Xlate('WM_IN', WOStepRec<WO_STEP_WM_IN_KEYS$>, 'CURR_STATUS', 'X')
                            RFWCnt              = Count(StatusCodes, 'RFW')
                            TotalUnprocessed    = RFWCnt * 25
                        end else
                            // All other reactor types use this method.
                            RDSKeys = WOStepRec<WO_STEP_RDS_KEY$>
                            If RDSKeys NE '' then
                                For Each RDSKey in RDSKeys using @VM
                                    RDSRec  = Database_Services('ReadDataRow', 'RDS', RDSKey)
                                    If RDSRec<RDS_DATE_IN$> EQ '' then
                                        TotalUnprocessed   += 25
                                    end
                                Next RDSKey
                            end
                        end
                    end
                Next WOStepKey
            end

            // Add the two.
            RemainingWafers = TotalUnreleased + TotalUnprocessed
        end
    end else
        Error_Services('Add', 'WorkOrderNo argument was missing from the ' : Service : ' service.')
    end

    Response    = RemainingWafers

end service


! This service should be deprecated as it returns inaccurate information for EpiPro.
! Use GetRemainingWafers service insead.
//----------------------------------------------------------------------------------------------------------------------
// GetUnprocessedWafers
//
// Returns the number of released wafers that have not yet been processed.
//----------------------------------------------------------------------------------------------------------------------
Service GetUnprocessedWafers(WorkOrderNo)

    RemainingWafers = ''

    If WorkOrderNo NE '' then
        WOLogRow    = Database_Services('ReadDataRow', 'WO_LOG', WorkOrderNo)

        If Error_Services('NoError') then
            // Get the total number of unreleased work orders.
            TotalWafers     = WOLogRow<WO_LOG_WO_QTY$>
            TotalReleased   = 0
            WOMatKeys       = WOLogRow<WO_LOG_WO_MAT_KEY$>
            For Each WOMatKey in WOMatKeys using @VM
                WOMatRec        = Database_Services('ReadDataRow', 'WO_MAT', WOMatKey)
                TotalReleased  += WOMatRec<WO_MAT_WAFER_QTY$>
            Next WOMatKey
            TotalUnreleased = TotalWafers - TotalReleased

            WOStepKeys          = WOLogRow<WO_LOG_WO_STEP_KEY$>
            TotalUnprocessed    = 0
            If WOStepKeys NE '' then
                ReactorType = WOLogRow<WO_LOG_REACT_TYPE$>
                For Each WOStepKey in WOStepKeys using @VM
                    WOStepRec   = Database_Services('ReadDataRow', 'WO_STEP', WOStepKey)
                    If Error_Services('NoError') then
                        // Get the total released but unprocessed.
                        RDSKeys = WOStepRec<WO_STEP_RDS_KEY$>
                        If RDSKeys NE '' then
                            For Each RDSKey in RDSKeys using @VM
                                RDSRec     = Database_Services('ReadDataRow', 'RDS', RDSKey)
                                WOMatKey   = Xlate('RDS', RDSKey, 'WO_MAT_KEY', 'X')
                                CurrStatus = Xlate('WO_MAT', WOMatKey, 'CURR_STATUS', 'X')
                                If ( (RDSRec<RDS_DATE_OUT$> EQ '') and (CurrStatus NE 'REJ') and (CurrStatus NE 'MT') ) then
                                    TotalUnprocessed += RDSRec<RDS_CASS_WAFER_QTY$>
                                end
                            Next RDSKey
                        end
                    end
                Next WOStepKey
            end

            // Add the two.
            RemainingWafers = TotalUnreleased + TotalUnprocessed
        end
    end else
        Error_Services('Add', 'WorkOrderNo argument was missing from the ' : Service : ' service.')
    end

    Response    = RemainingWafers

end service


//----------------------------------------------------------------------------------------------------------------------
// GetCurrCassNo
//
// Returns the next cassette number to be unloaded.
//----------------------------------------------------------------------------------------------------------------------
Service GetCurrCassNo(WorkOrderNo)
    
    If RowExists('WO_LOG', WorkOrderNo) then
        CurrCassNo = ''
        DateIn     = ''
        RDSKeys    = Xlate('WO_STEP', WorkOrderNo:'*1', 'RDS_KEY', 'X')
        NumKeys    = DCount(RDSKeys, @VM)
        For KeyIndex = NumKeys to 1 Step -1
            RDSKey = RDSKeys<0, KeyIndex>
            DateIn = Xlate('RDS', RDSKey, 'DATE_IN', 'X')
        Until DateIn NE ''
        Next KeyIndex
        If DateIn NE '' then CurrCassNo = Xlate('RDS', RDSKey, 'CASS_NO', 'X')
        Response = CurrCassNo
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// GetLastCassOut
//
// Returns the last cassette number unloaded.
//----------------------------------------------------------------------------------------------------------------------
Service GetLastCassOut(WorkOrderNo)
    
    If RowExists('WO_LOG', WorkOrderNo) then
        LastCassOut = ''
        DateOut     = ''
        RDSKeys     = Xlate('WO_STEP', WorkOrderNo:'*1', 'RDS_KEY', 'X')
        NumKeys     = DCount(RDSKeys, @VM)
        For KeyIndex = NumKeys to 1 Step -1
            RDSKey  = RDSKeys<0, KeyIndex>
            DateOut = Xlate('RDS', RDSKey, 'DATE_OUT', 'X')
        Until DateOut NE ''
        Next KeyIndex
        If DateOut NE '' then LastCassOut = Xlate('RDS', RDSKey, 'CASS_NO', 'X')
        Response = LastCassOut
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// GetNextRunNo
//
// Returns the next run number in the work order.
//----------------------------------------------------------------------------------------------------------------------
Service GetNextRunNo(WorkOrderNo, WorkOrderStep)
    
    hSysLists   = Database_Services('GetTableHandle', 'SYSLISTS')
    HaveLock    = False$
    TimeElapsed = 0
    RequestTime = Time()
    If WorkOrderNo NE '' then
        Loop
            Lock hSysLists, ServiceKeyID then
                HaveLock = True$     
                If WorkOrderStep = '' then WorkOrderStep = 1
                StepRDSNos = XLATE('WO_STEP', WorkOrderNo:'*':WorkOrderStep, 'RDS_KEY', 'X')
                CurrRuns   = 0
                NumRDS     = DCount(StepRDSNos, @VM)
                For I = 1 to NumRDS
                    If RowExists('RDS', StepRDSNos<1,I>) then
                        CurrRuns += 1
                    end
                next I
                NextRunNo = CurrRuns + 1
                Response = NextRunNo       
                Unlock hSysLists, ServiceKeyID else Null
            end
            TimeElapsed = Time() - RequestTime
        Until HaveLock EQ True$ or TimeElapsed GT 60
        Repeat  
    end
    If TimeElapsed GT 60 then
        Error_Services('Add', 'Error occurred within the ':Service:' service module')
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// CreateOutbound
//
// Creates an empty outbound cassette for a GaN work order.
//----------------------------------------------------------------------------------------------------------------------
Service CreateOutbound(WONo)
 
    If WONo NE '' then
        WORec        = Database_Services('ReadDataRow', 'WO_LOG', WONo)
        CassNos      = WORec<WO_LOG_WO_MAT_KEY$>
        LastCassNo   = DCount(CassNos, @VM)
        NextCassNo   = LastCassNo + 1
        LastCassID   = WONo : '*' : LastCassNo
        NewCassID    = WONo : '*' : NextCassNo
        SchedCassQty = Xlate('WO_MAT', LastCassID, 'SCHED_QTY', 'X')
        WOMatWfrRec  = Database_Services('ReadDataRow', 'WO_MAT_WFR', NewCassID)
        If WOMatWfrRec EQ '' then
            for WfrCount = 1 to SchedCassQty
                WOMatWfrRec<WO_MAT_WFR_OUT_PREV_WFR_ID$,WfrCount> = NewCassID : '*' : WfrCount
            Next WfrCount
            Database_Services('WriteDataRow', 'WO_MAT_WFR', NewCassID, WOMatWfrRec)                        
            WOMatRecToCopy                             = Database_Services('ReadDataRow', 'WO_MAT', LastCassID)
            WOMatRecToCopy<WO_MAT_RDS_FINAL_SIG_ORG$>  = ''
            WOMatRecToCopy<WO_MAT_CASS_FINAL_SIG_DTM$> = ''
            Database_Services('WriteDataRow', 'WO_MAT', NewCassID, WOMatRecToCopy)           
            WORec                                      = Insert(WORec, WO_LOG_WO_MAT_KEY$, -1, 0, NewCassID)
            Database_Services('WriteDataRow', 'WO_LOG', WONo, WORec)           
        end else
            ErrorMessage = 'Error in service ':Service:'. WO_MAT_WFR record ':NewCassID:' already exists!'
            Error_Services('Add', ErrorMessage)
        end
    end
    
end service


Service UpdateUnscheduledQuantities(WOList)

    hSysLists = Database_Services('GetTableHandle', 'SYSLISTS')
    Lock hSysLists, ServiceKeyID then
        SchedList       = Database_Services('ReadDataRow', 'APP_INFO', 'SCHED_WO_LIST')
        // Select WO_LOG rows that do not have a closed date.
        OpenWOLogKeyIDs = ''
        If WOList NE '' then OpenWOLogKeyIDs = WOList
        If OpenWOLogKeyIDs EQ '' then
            Open 'DICT.WO_LOG' to hWOLogDict then
                Set_Status(0)
                Flag      = ''
                Btree.Extract('CLOSE_DATE' : @VM : '' : @FM, 'WO_LOG', hWOLogDict, OpenWOLogKeyIDs, 'S', Flag)
                If Flag NE 0 then
                    Status = Get_Status(errCode)
                    Error_Services('Add', 'Error in ':Service:' service. Error code ':errCode:'.')
                end
            end
        end
        If ( Error_Services('NoError') and (OpenWOLogKeyIDs NE '') ) then
            SchedList = SRP_Array('Rotate', SchedList, @FM, @VM)
            For Each WONo in OpenWOLogKeyIDs using @VM setting fPos
                UnscheduledWfrQty = Schedule_Services('GetUnscheduledWfrQty', WONo)
                WONos             = SchedList<1>
                If UnscheduledWfrQty GT 0 then
                    Locate WONo in WONos using @VM setting vPos else vPos = -1
                    WOLogRow        = Database_Services('ReadDataRow', 'WO_LOG', WONo)
                    EpiPartNo       = WOLogRow<WO_LOG_EPI_PART_NO$>
                    CustNo          = WOLogRow<WO_LOG_CUST_NO$>
                    CompanyRow      = Database_Services('ReadDataRow', 'COMPANY', CustNo)
                    CustName        = CompanyRow<COMPANY_ABBREV$>
                    ReactorType     = WOLogRow<WO_LOG_REACT_TYPE$>
                    SchedList<1, vPos> = WONo
                    SchedList<2, vPos> = EpiPartNo
                    SchedList<3, vPos> = CustName
                    SchedList<4, vPos> = ReactorType
                    WOSchedRec         = Database_Services('ReadDataRow', 'WO_SCHEDULE_NG', WONo)
                    CurrVal            = WOSchedRec<WO_SCHEDULE_NG.UNSCHED_QTY$>
                    If CurrVal NE UnscheduledWfrQty then
                        WOSchedRec<WO_SCHEDULE_NG.UNSCHED_QTY$> = UnscheduledWfrQty
                        Database_Services('WriteDataRow', 'WO_SCHEDULE_NG', WONo, WOSchedRec, True$, False$, True$)
                    end
                end else
                    Locate WONo in WONos using @VM setting vPos then
                        SchedList = Delete(SchedList, 1, vPos, 0)
                        SchedList = Delete(SchedList, 2, vPos, 0)
                        SchedList = Delete(SchedList, 3, vPos, 0)
                        SchedList = Delete(SchedList, 4, vPos, 0)
                        Database_Services('DeleteDataRow', 'WO_SCHEDULE_NG', WONo)
                    end
                end
            Next WONo
        end
        SchedList = SRP_Array('Rotate', SchedList, @FM, @VM)
        If WOList EQ '' then
            // Clear out WO_SCHEDULE_NG table of work orders that no longer have any wafers left to schedule.
            EOF   = False$
            Query = 'SELECT WO_SCHEDULE_NG WITH UNSCHED_QTY EQ 0'
            RList(Query, TARGET_ACTIVELIST$, '', '', '')
            Loop
                Readnext WOLogKeyID else EOF = True$
            Until EOF = True$
                Database_Services('DeleteDataRow', 'WO_SCHEDULE_NG', WOLogKeyID)
            Repeat
        end
        SchedList = SRP_Array('SortSimpleList', SchedList, 'DescendingNumbers', @FM)
        Database_Services('WriteDataRow', 'APP_INFO', 'SCHED_WO_LIST', SchedList)
        Unlock hSysLists, ServiceKeyID else Null
    end
    
end service


Service ReleaseCassettes(WONo)

    NumRelCass = 0
    ErrorMsg   = ''
    If WONo NE '' then
    
        PSNo   = Xlate('WO_LOG', WONo, 'PS_NO', 'X')
        PSMode = Xlate('PROD_SPEC',PSNo,'SPEC_TYPE','X')
        
        If PSMode = 'Q' OR PSMode = 'U' then
            
            ReactType = Xlate('WO_LOG', WONo, 'REACT_TYPE', 'X')
            
            If ReactType = 'EPP' OR ReactType = 'EpiPro' then
                
                OutLoadQty = Xlate('WO_LOG', WONo, 'CUST_EPI_PART_WMO_LOAD_CNT', 'X')
                If OutLoadQty = '' then
                    ErrorMsg = 'Outbound Load Cnt is required for ':OCONV(PSMode,'[PROD_SPEC_MODE_CONV]'):' Work Orders.'
                end                
                
            end else
                
                OutLoadQty = Xlate('WO_LOG', WONo, 'CUST_EPI_PART_SHIP_QTY', 'X')
                If OutLoadQty = '' then
                    ErrorMsg = 'Cass Ship Qty is required for ':OCONV(PSMode,'[PROD_SPEC_MODE_CONV]'):' Work Orders.'
                end                
            end            
        end
        
        If ErrorMsg EQ '' then
            
            CassList   = Xlate('WO_LOG', WONo, 'WO_MAT_CASS_NO', 'X')
            RelDTMs    = Xlate('WO_LOG', WONo, 'WO_MAT_REL_DTM', 'X')
            SAPCassIDs = Xlate('WO_LOG', WONo, 'WO_MAT_CASS_ID_SAP', 'X')
            CassStatus = Xlate('WO_LOG', WONo, 'WO_MAT_CURR_STATUS', 'X')
            
            Loop
                LastLine = CassList[-1,'B':@VM]
            Until LastLine<1,1> NE '' OR LastLine = ''
                CassList[COL1(),COL2()] = ''
            Repeat
            
            CassCnt = DCount(CassList, @VM)
            
            //(SAPCassIDs<0, I> = '')

            UnReleasedCassNos = ''
            For I = 1 to CassCnt
                If ( (RelDTMs<0, I> = '') and (CassStatus<0, I> EQ '') ) then
                    ErrorMsg   = 'Error in obj_WO_Log("ReleaseCassettes"). Cass status is null for ':CassList<0, I>:'!'
                    LogData    = ''
                    LogData<1> = LoggingDTM
                    LogData<2> = @USER4
                    LogData<3> = ErrorMsg
                    Machine    = Environment_Services('GetServer')
                    If Machine NE 'MESSA01EC' then
                        EmailAddr  = 'dstieber@srpcs.com,6613649828@txt.att.net'
                        Logging_Services('AppendLog', objReleaseLog, LogData, @RM, @FM, False$, EmailAddr, ErrorMsg)
                    end else
                        Logging_Services('AppendLog', objReleaseLog, LogData, @RM, @FM, False$)       
                    end
                end
                If ErrorMsg EQ '' then
                    // Log work order, cassette number, status, and release DTM in the event that a release
                    // issue occurs again.
                    LogData    = ''
                    LogData<1> = LoggingDTM
                    LogData<2> = @User4
                    LogData<3> = WONo
                    LogData<4> = CassList<0, I>       ; // CassNo
                    LogData<5> = CassStatus<0, I>     ; // Cass Status
                    LogData<6> = Quote(RelDTMs<0, I>) ; // Release DTM
                    Logging_Services('AppendLog', objReleaseLog, LogData, @RM, @FM, False$)
                    If ( (RelDTMs<0, I> = '') and ( (CassStatus<0, I> = 'RX') or (CassStatus<0, I> = 'MT'))) then
                        UnReleasedCassNos<1,-1> = CassList<0, I>
                    end
                end
            Until ErrorMsg NE ''
            Next I

            If ErrorMsg EQ '' then
                
                TempWONo = WONo
                Set_Status(0)

                obj_WO_Log('ReleaseCassettes',TempWONo:@RM:UnReleasedCassNos)		;* This variable gets changed down the line       
                errCode = ''
                
                If Get_Status(errCode) then
                    
                    ErrorMsg = 'Error in obj_WO_Log("ReleaseCassettes"). Error code ':errCode:'.'
                    Database_Services('ReleaseKeyIDLock', 'WO_LOG', WONo)
                    
                end else       
                
                    NumRelCass = CassCnt
                    OrderNo    = Get_Property(@WINDOW:'.ORD_NO','TEXT')
                    
                    Recipients = XLATE('NOTIFICATION','WO_ENTRY',NOTIFICATION_USER_ID$,'X')
                    SentFrom   = @USER4
                    Subject    = 'Work Order Released ':WONo
                    Message    = 'Work Order Released to Production.'
                    NewForm    = Xlate('APP_INFO', 'NEW_WO_FORM', '', 'X')
                    If NewForm then
                        AttachWindow	= 'NDW_WO_LOG'
                    end else
                        AttachWindow	= 'WO_LOG2'
                    end
                    AttachKey		= WONo
                    SendToGroup		= ''   
                    
                    Parms = Recipients:@RM:SentFrom:@RM:Subject:@RM:Message:@RM:AttachWindow:@RM:AttachKey:@RM:SendToGroup
                    obj_Notes('Create',Parms)
                end
            end
        end
    end
    
    Work_Order_Services('UpdateReleasedQty', WONo)
    
    If ErrorMsg NE '' then Error_Services('Add', ErrorMsg)
    Response = DCount(UnReleasedCassNos, @VM)
    
end service


Service PrintLabels(WONo)

    If WONo NE '' then
    
        WOSteps = XLATE('WO_LOG', WONo, WO_LOG_WO_STEP_KEY$, 'X')
        
        If INDEX(WOSteps,@VM,1) then
            Buttons = ''
            FOR I = 1 TO COUNT(WOSteps,@VM) + (WOSteps NE '')
                Buttons<I> = 'Step ':I
            NEXT I
            SWAP @FM WITH ',' IN Buttons
            Buttons = 'B':Buttons
            
            TypeOver = ''
            TypeOver<MTEXT$> 	= 'Which Work Order step do you wish to print labels for?'
            TypeOver<MTYPE$> 	= Buttons
            TypeOver<MICON$> 	= '?'
            TypeOver<MCAPTION$>	= 'MultiStep WorkOrder Selection'
            WOStep = Msg(@window, TypeOver)
            MultiStepFlag = WOStep
        end ELSE
            WOStep = 1
            MultiStepFlag = 0
        end
        
        If WOStep = 1 then
            PSNo = XLATE('WO_STEP',WONo:'*1',WO_STEP_PROD_SPEC_ID$,'X')
            ReactorType = XLATE('PROD_SPEC',PSNo,PROD_SPEC_REACTOR_TYPE$,'X')
            
            If ReactorType = 'GAN' then
                Print_WO_Mat_In_Labels(WONo,'')
                Print_WO_Mat_Out_Labels(WONo,'')
            end
            
            If ReactorType = 'EPP' OR ReactorType = 'P' then
                Print_WMI_Labels(WONo,1)
                Print_WMO_Labels(WONo,1)
            end
        end
        
        NonEpiPro = ( (ReactorType NE 'GAN') and (ReactorType NE 'EPP') and (ReactorType NE 'P') )
        
        If NonEpiPro then
            
            If WOStep > 1 then
                
                Def = ""
                Def<MTEXT$> = "Adjusting Step ":WOStep:" Scheduled Quantities..."
                Def<MTYPE$> = "U"
                
                MsgUp = Msg(@window, Def)
                
                RDSNos = obj_WO_Step('AdjStepRDSQtys', WOStep:@RM:WOSteps)
                
                If Get_Status(errCode) then
                    
                    ErrMsg(errCode)											;* Display error from the update
                    Msg(@WINDOW,MsgUp)										;* Take down the processing message
                    
                    RETURN
                end
                Msg(@WINDOW,MsgUp)
                
            end ELSE
                RDSNos = XLATE('WO_STEP',WOSteps<1,1>,WO_STEP_RDS_KEY$,'X')
            end
            
            If MemberOf(@USER4,'OI_ADMIN') then
                WOMatKeys = XLATE('WO_LOG',WONo,WO_LOG_WO_MAT_KEY$,'X')
                CONVERT @VM TO @FM IN WOMatKeys
                AllRDSNos = XLATE('WO_MAT',WOMatKeys,WO_MAT_RDS_NO$,'X')
                
                RDSNos = ''
                
                LOOP
                    CassRDSNos = AllRDSNos[1,@FM]
                    AllRDSNos[1,COL2()] = ''
                UNTIL CassRdsNos = ''
                    RDSNos := CassRdsNos<1,WOStep>:@VM
                REPEAT
                
                RDSNos[-1,1] = ''
                
            end
            
            If INDEX(RDSNos,@VM,1) then
                TypeOver = ''
                TypeOver<PDISPLAY$> = RDSNos
                TypeOver<PFIELD$>	= 2
                TypeOver<PTYPE$>	= 'K'
                
                RDSKeys = Popup(@WINDOW,TypeOver,'WO_LOG_RTS')
                
            end ELSE
                RDSKeys = RDSNos
            end
            
            If RDSKeys = '' then 
                ErrMsg('No RDS Numbers Selected or Work Order Not Yet Released to Production.')
            end ELSE
                CONVERT @VM TO @FM IN RDSKeys
                
                Print_Cass_Labels(RDSKeys,MultiStepFlag)				;* Updated labels with Akrion Bar Codes
                
            end
        end
    end
    
end service


Service UpdateWOStepStatus(WONo)
    
    If WONo NE '' then
        WOStepKey        = WONo:'*':1
        WOStepRec        = Database_Services('ReadDataRow', 'WO_STEP', WOStepKey)
        WOStepCurrStatus = obj_WO_Step('CurrStatus', WOStepKey:@RM:WOStepRec)
*        HaveLock         = Database_Services('GetKeyIDLock', 'WO_STEP', WOStepKey)
*        If HaveLock then
            // Get a fresh copy of the record
            WOStepRec              = Database_Services('ReadDataRow', 'WO_STEP', WOStepKey)
            WOStepCurrStatusStatic = WOStepRec<WO_STEP_CURR_STATUS_STATIC$>
            If WOStepCurrStatus NE WOStepCurrStatusStatic then
                WOStepRec<WO_STEP_CURR_STATUS_STATIC$> = WOStepCurrStatus
                Database_Services('WriteDataRow', 'WO_STEP', WOStepKey, WOStepRec, True$, False$, True$)
            end
            Database_Services('ReleaseKeyIDLock', 'WO_STEP', WOStepKey)
*        end else
*            // Failed to lock record for update. Throw an error to keep the request in the queue.
*            Error_Services('Add', 'Failed to lock WO_STEP record ':WOStepKey:' in ':Service:' service.')
*        end
    end
    
end service


Service PostWOStepUpdateRequest(WONo)

    If WONo NE '' then
        StepNo    = 1
        WOStepKey = WONo:'*':StepNo
        // Look for a pre-existing request in the queue
        If (RowExists('WO_STEP', WOStepKey) EQ False$) then
            // This is a new request
            RequestKeyID                            = WOStepKey
            RequestRow                              = ''
            RequestRow<WO_STEP_CURR_STATUS_STATIC$> = Datetime()
            Database_Services('WriteDataRow', 'WO_STEP_QUEUE', RequestKeyID, RequestRow, False$, False$, False$)
        end else
            // Duplicate request
            Null
        end
    end else
        Error_Services('Add', 'Null parameter passed into service call. All parameters are required.')
    end
    
end service


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


Service SetHotFlag(WONo, HotFlag=BOOLEAN)
    
    If ( ( RowExists('WO_LOG', WONo) and ( (HotFlag EQ True$) or (HotFlag EQ False$) ) ) ) then
        Open 'WO_LOG' to hTable then
            WriteV HotFlag on hTable, WONo, WO_LOG_HOT_FLAG$ else
                Error_Services('Add', 'Error in ':Service:' service. File error: ':@File_Error)
            end
        end
    end else
        Error_Services('Add', 'Error in ':Service:' service. Invalid parameters passed into service.')
    end
    
end service


Service GetHotFlag(WONo)
    
    HotFlag = ''
    If RowExists('WO_LOG', WONo) then
        Open 'WO_LOG' to hTable then
            ReadV HotFlag from hTable, WONo, WO_LOG_HOT_FLAG$ else
                Error_Services('Add', 'Error in ':Service:' service. File error: ':@File_Error)
            end
        end
    end else
        Error_Services('Add', 'Error in ':Service:' service. Invalid parameters passed into service.')
    end
    Response = HotFlag
    
end service


Service UpdateReleasedQty(WONo)

    WORec = Database_Services('ReadDataRow', 'WO_LOG', WONo)
    If Error_Services('NoError') then
        RelQty                          = obj_WO_Log('RelQty', WONo:@RM:WORec)
        WORec<WO_LOG_REL_QTY_STATIC$>   = RelQty
        WOQty                           = WORec<WO_LOG_QTY$>
        If RelQty GT WOQty then
            UnRelQty = 0
        end else
            UnRelQty = WOQty - RelQty
        end
        WORec<WO_LOG_UNREL_QTY_STATIC$> = UnRelQty
        Database_Services('WriteDataRow', 'WO_LOG', WONo, WORec)
    end
    
end service


Service UpdateReceivedQty(WONo)

    WORec = Database_Services('ReadDataRow', 'WO_LOG', WONo)
    If Error_Services('NoError') then
        RXQty                        = obj_WO_Log('RxQty', WONo:@RM:WORec)
        WORec<WO_LOG_RX_QTY_STATIC$> = RXQty
        Database_Services('WriteDataRow', 'WO_LOG', WONo, WORec)
    end 
    
end service


Service UpdateShippedQty(WONo)

    WORec = Database_Services('ReadDataRow', 'WO_LOG', WONo)
    If Error_Services('NoError') then
        ShipQty                        = obj_WO_Log('ShipQty', WONo:@RM:WORec)
        WORec<WO_LOG_SHIP_QTY_STATIC$> = ShipQty
        Database_Services('WriteDataRow', 'WO_LOG', WONo, WORec)
    end
    
end service

Service ConvertRecordToJSON(WONo, Record, ItemURL)
    jsonRecord    = ''
    IF WONo NE '' then
        If Record EQ '' then Record = Database_Services('ReadDataRow', 'WO_LOG', WONo)
        If Error_Services('NoError') then
            @DICT   = Database_Services('GetTableHandle', 'DICT.WO_LOG')
            @ID     = WONo
            @RECORD = Record
            objJSON = ''
            If SRP_JSON(objJSON, 'New', 'Object') then
            	objWOLog = ''
                If SRP_JSON(objWOLog, 'New', 'Object') then
                    SRP_JSON(objWOLog, 'SetValue', 'KeyId', @ID)
                    SRP_JSON(objWOLog, 'SetValue', 'ProdOrdNo', {PROD_ORD_NO})
                    SRP_JSON(objWOLog, 'SetValue', 'PSN', {PROD_SPEC_ID})
                    SRP_JSON(objWOLog, 'SetValue', 'EpiPartNo', {EPI_PART_NO})
                    SRP_JSON(objWOLog, 'SetValue', 'HOT_FLAG', {HOT_FLAG})
                    //Create a list of cassettes
                    CassIDs           = {WO_MAT_KEY}
                    objChildCassettes = ''
                    If SRP_JSON(objChildCassettes, 'New', 'Array') then
                        If CassIDs NE '' then
                            for each Cass in CassIDs using @VM setting vPos
                                SAPBatchNo       = XLATE('WO_MAT', Cass, WO_MAT_SAP_BATCH_NO$, 'X')
                                SAPTXDtm         = XLATE('WO_MAT', Cass, WO_MAT_SAP_TX_DTM$, 'X')
                                RDSNo            = XLATE('WO_MAT', Cass, WO_MAT_RDS_NO$, 'X')
                                ShipNo           = XLATE('WO_MAT', Cass, WO_MAT_SHIP_NO$, 'X')
                                //Add in each indv. cassette object
                                objChildCassette = ''
                                If SRP_JSON(objChildCassette, 'New', 'Object') then
                                    SRP_JSON(objChildCassette, 'SetValue', 'KeyId', Cass)
                                    SRP_JSON(objChildCassette, 'SetValue', 'SAPBatchNo', SAPBatchNo)
                                    SRP_JSON(objChildCassette, 'SetValue', 'SAPTXDtm', OCONV(SAPTXDtm, 'DT'))
                                    SRP_JSON(objChildCassette, 'SetValue', 'RDSNo', RDSNo)
                                    SRP_JSON(objChildCassette, 'SetValue', 'ShipNo', ShipNo)
                                    SRP_JSON(objChildCassettes, 'Add', objChildCassette)
                                    SRP_JSON(objChildCassette, 'Release')
                                end
                                
                            Next Cass
                        end else
                            SRP_JSON(objWOLog, 'SetValue', 'ChildCassettes', '')
                        end
                        SRP_JSON(objWOLog, 'Set', 'ChildCassettes', objChildCassettes)
                        SRP_JSON(objChildCassettes, 'Release')
                    end
                    SRP_JSON(objJSON, 'Set', 'WO_Log', objWOLog)
                    SRP_JSON(objWOLog, 'Release')
                end
                If itemURL NE '' then
                    // The itemURL was passed in so add HAL+JSON properties.
                    // Create the _links property and then all link objects needed for this resource.
                    objLinks = ''
                    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(objJSON, 'Set', '_links', objLinks)
                        SRP_JSON(objLinks, 'Release')
                    end

                    // Create the _class property for this resource.
                    SRP_JSON(objJSON, 'SetValue', '_class', 'resource')
                end
                jsonRecord = SRP_JSON(objJSON, 'Stringify', 'Styled')
                SRP_JSON(objJSON, 'Release')
            end else
                Error_Services('Add', 'Unable to create JSON representation in the ' : Service : ' service.')
            end
        end
    end else
        Error_Services('Add', 'KeyID argument was missing in the ' : Service : ' service.')
    end
    Response    = jsonRecord
end service


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

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

