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
$Insert WM_IN_EQUATES
$Insert WM_OUT_EQUATES
$Insert VOIDED_LOT_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, Service_Services
Declare subroutine  obj_WO_Mat_Log, WO_Mat_Services, Work_Order_Services, Transaction_Services, Extract_Si_Keys
Declare subroutine  Mona_Services, Lot_Event_Services, RDS_Services, Lot_Services, WM_In_Services, WM_Out_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, Hold_Services, Signature_Services, Lot_Services, RTI_CreateGUID

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$)

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] : ' Void Log.csv'
Headers     = 'Logging DTM' : @FM : 'User' : @FM : 'CassetteId' : @FM : 'Notes'
objVoidLog  = 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)
            
            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
                    Logging_Services('AppendLog', objReleaseLog, LogData, @RM, @FM, False$)       
                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'
                TypeOver<PSHOWGAUGE$> = True$
                
                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)
		// 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
    
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)
	
	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] : ' Update Released Qty Log.csv'
	Headers      = 'Logging DTM' : @FM : 'WONo' : @FM : 'Notes'
	objUpRelLog  = Logging_Services('NewLog', LogPath, LogFileName, CRLF$,',', Headers, '', False$, False$)
	LoggingDTM   = LogDate : ' ' : LogTime   ; // Logging DTM	
	RelQty       = ''
	UnRelQty     = ''
	OrigRelQty   = ''
	OrigUnRelQty = ''
	ErrorMsg     = ''
	If WONo NE '' then
		If RowExists('WO_LOG', WONo) then
			WORec = Database_Services('ReadDataRow', 'WO_LOG', WONo)
			If Error_Services('NoError') then 
				RelQty = obj_WO_Log('RelQty', WONo:@RM:WORec)
				WOQty  = WORec<WO_LOG_QTY$>
				If RelQty GT WOQty then
					UnRelQty = 0
				end else
					UnRelQty = WOQty - RelQty
				end				
				Open 'WO_LOG' to hTable then
					Read WORec from hTable, WONo then
						OrigRelQty   = WORec<WO_LOG_REL_QTY_STATIC$>
						OrigUnRelQty = WORec<WO_LOG_UNREL_QTY_STATIC$>
						If ( (OrigRelQty NE RelQty) or (OrigUnRelQty NE UnRelQty) ) then
							WORec<WO_LOG_REL_QTY_STATIC$>   = RelQty
							WORec<WO_LOG_UNREL_QTY_STATIC$> = UnRelQty
							Write WORec on hTable, WONo else
								ErrorMsg  = 'Error in ':Service:' service. Failed to write unreleased quantity ':UnRelQty
								ErrorMsg := ' and/or UNREL_QTY_STATIC field of WO_LOG record ':WONo:'.'
								ErrorMsg := '@FILE_ERROR: ':@FILE_ERROR								
							end
						end
					end
				end else
					ErrorMsg = 'Error in ':Service:' service. Failed to open the WO_LOG table.'
				end
			end else
				ErrorMsg  = 'Error in ':Service:' service. Failed to read record ':WONo:' from the WO_LOG table. '
				ErrorMsg := 'Error message: ':Error_Services('GetMessage')
			end
		end else
			ErrorMsg = 'Error in ':Service:' service. ':WONo:' does not exist in the WO_LOG table.'
		end
	end else
		ErrorMsg = 'Error in ':Service:' service. Null WONo passed into service'
	end
    
    LogData    = ''
    LogData<1> = LoggingDtm
    LogData<2> = WONo
    If ErrorMsg EQ '' then
    	LogResult   = 'Successfully updated WO_LOG record ':WONo:' field REL_QTY_STATIC with released quantity ':RelQty
    	LogResult  := ' and field UNREL_QTY_STATIC with unreleased quantity ':UnRelQty:'.'
    	LogData<3>  = LogResult
    	Logging_Services('AppendLog', objUpRelLog, LogData, @RM, @FM)
    end else
    	LogResult   = 'Failed to update WO_LOG record ':WONo:' field REL_QTY_STATIC with released quantity ':RelQty
    	LogResult  := ' or field UNREL_QTY_STATIC with unreleased quantity ':UnRelQty:'. Error message: ':ErrorMsg
    	LogData<3>  = LogResult
    	Logging_Services('AppendLog', objUpRelLog, LogData, @RM, @FM)
    	Error_Services('Add', ErrorMsg)
    end		
    
end service


Service UpdateReceivedQty(WONo)

	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] : ' Update Received Qty Log.csv'
	Headers     = 'Logging DTM' : @FM : 'WONo' : @FM : 'Notes'
	objUpRecLog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$,',', Headers, '', False$, False$)
	LoggingDTM  = LogDate : ' ' : LogTime   ; // Logging DTM	
	RXQty       = ''
	OrigRXQty   = ''
	ErrorMsg    = ''
	If WONo NE '' then
		If RowExists('WO_LOG', WONo) then
			WORec = Database_Services('ReadDataRow', 'WO_LOG', WONo)
			If Error_Services('NoError') then 
				RXQty = obj_WO_Log('RxQty', WONo:@RM:WORec)
				Open 'WO_LOG' to hTable then
					ReadV OrigRXQty from hTable, WONo, WO_LOG_RX_QTY_STATIC$ then
						If OrigRXQty NE RXQty then 
							WriteV RXQty on hTable, WONo, WO_LOG_RX_QTY_STATIC$ else
								ErrorMsg  = 'Error in ':Service:' service. Failed to write received quantity ':RXQty
								ErrorMsg := ' to the RX_QTY_STATIC field of WO_LOG record ':WONo:'.'
								ErrorMsg := '@FILE_ERROR: ':@FILE_ERROR
							end
						end
					end else
						ErrorMsg = 'Error in ':Service:' service. Error reading RX_QTY_STATIC column from WO_LOG ':WONo:'.'
					end
				end else
					ErrorMsg = 'Error in ':Service:' service. Failed to open the WO_LOG table.'
				end
			end else
				ErrorMsg  = 'Error in ':Service:' service. Failed to read record ':WONo:' from the WO_LOG table. '
				ErrorMsg := 'Error message: ':Error_Services('GetMessage')
			end
		end else
			ErrorMsg = 'Error in ':Service:' service. ':WONo:' does not exist in the WO_LOG table.'
		end
	end else
		ErrorMsg = 'Error in ':Service:' service. Null WONo passed into service'
	end
    
    LogData    = ''
    LogData<1> = LoggingDtm
    LogData<2> = WONo
    If ErrorMsg EQ '' then
    	LogData<3> = 'Successfully updated WO_LOG record ':WONo:' field RX_QTY_STATIC with received quantity ':RXQty:'.'
    	Logging_Services('AppendLog', objUpRecLog, LogData, @RM, @FM)
    end else
    	LogResult   = 'Failed to update WO_LOG record ':WONo:' field RX_QTY_STATIC with received quantity ':RXQty
    	LogResult  := '. Error message: ':ErrorMsg
    	LogData<3>  = LogResult
    	Logging_Services('AppendLog', objUpRecLog, LogData, @RM, @FM)
    	Error_Services('Add', ErrorMsg)
    end
    
end service


Service UpdateShippedQty(WONo)
	
	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] : ' Update Shipped Qty Log.csv'
	Headers      = 'Logging DTM' : @FM : 'WONo' : @FM : 'Notes'
	objUpShipLog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$,',', Headers, '', False$, False$)
	LoggingDTM   = LogDate : ' ' : LogTime   ; // Logging DTM	
	ShipQty      = ''
	OrigShipQty  = ''
	ErrorMsg     = ''
	If WONo NE '' then
		If RowExists('WO_LOG', WONo) then
			WORec = Database_Services('ReadDataRow', 'WO_LOG', WONo)
			If Error_Services('NoError') then 
				ShipQty = obj_WO_Log('ShipQty', WONo:@RM:WORec)
				Open 'WO_LOG' to hTable then
					ReadV OrigShipQty from hTable, WONo, WO_LOG_SHIP_QTY_STATIC$ then
						If OrigShipQty NE ShipQty then 
							WriteV ShipQty on hTable, WONo, WO_LOG_SHIP_QTY_STATIC$ else
								ErrorMsg  = 'Error in ':Service:' service. Failed to write ':ShipQty
								ErrorMsg := ' to the SHIP_QTY_STATIC field of WO_LOG record ':WONo:'.'
								ErrorMsg := '@FILE_ERROR: ':@FILE_ERROR
							end
						end
					end else
						ErrorMsg = 'Error in ':Service:' service. Error reading SHIP_QTY_STATIC column from WO_LOG ':WONo:'.'
					end
				end else
					ErrorMsg = 'Error in ':Service:' service. Failed to open the WO_LOG table.'
				end
			end else
				ErrorMsg  = 'Error in ':Service:' service. Failed to read record ':WONo:' from the WO_LOG table. '
				ErrorMsg := 'Error message: ':Error_Services('GetMessage')
			end
		end else
			ErrorMsg = 'Error in ':Service:' service. ':WONo:' does not exist in the WO_LOG table.'
		end
	end else
		ErrorMsg = 'Error in ':Service:' service. Null WONo passed into service'
	end
    
    LogData    = ''
    LogData<1> = LoggingDtm
    LogData<2> = WONo
    If ErrorMsg EQ '' then
    	LogResult   = 'Successfully updated WO_LOG record ':WONo:' field SHIP_QTY_STATIC '
    	LogResult  := 'with shipped quantity ':ShipQty:'.'
    	LogData<3>  = LogResult
    	Logging_Services('AppendLog', objUpShipLog, LogData, @RM, @FM)
    end else
    	LogResult   = 'Failed to update WO_LOG record ':WONo:' field SHIP_QTY_STATIC with shipped quantity '
    	LogResult  := ShipQty:'. Error message: ':ErrorMsg
    	LogData<3>  = LogResult
    	Logging_Services('AppendLog', objUpShipLog, LogData, @RM, @FM)
    	Error_Services('Add', ErrorMsg)
    end	
    
end service


Service UpdateScrappedQty(WONo)

	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] : ' Update Scrapped Qty Log.csv'
	Headers       = 'Logging DTM' : @FM : 'WONo' : @FM : 'Notes'
	objUpScrapLog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$,',', Headers, '', False$, False$)
	LoggingDTM    = LogDate : ' ' : LogTime
	ScrapQty      = ''
	OrigScrapQty  = ''
	ErrorMsg      = ''
	If WONo NE '' then
		If RowExists('WO_LOG', WONo) then
			WORec = Database_Services('ReadDataRow', 'WO_LOG', WONo)
			If Error_Services('NoError') then
				EpiProFlag = Xlate('WO_LOG', WONo, 'EPI_PRO_FLAG', 'X')
				If EpiProFlag then
					 ScrapQty = Xlate('WO_STEP', WONo:'*1', 'TOT_REJ_TOTAL', 'X')
				end else
					 ScrapQty = Sum(Xlate('WO_LOG', WONo, 'WO_MAT_SAP_CONFIRM_SCRAP', 'X'))
				end					
				Open 'WO_LOG' to hTable then
					ReadV OrigScrapQty from hTable, WONo, WO_LOG_SCRAP_QTY_STATIC$ then
						If OrigScrapQty NE ScrapQty then 
							WriteV ScrapQty on hTable, WONo, WO_LOG_SCRAP_QTY_STATIC$ else
								ErrorMsg  = 'Error in ':Service:' service. Failed to write ':ScrapQty
								ErrorMsg := ' to the SCRAP_QTY_STATIC field of WO_LOG record ':WONo:'.'
								ErrorMsg := '@FILE_ERROR: ':@FILE_ERROR
							end
						end
					end else
						ErrorMsg = 'Error in ':Service:' service. Error reading SCRAP_QTY_STATIC column from WO_LOG ':WONo:'.'
					end
				end else
					ErrorMsg = 'Error in ':Service:' service. Failed to open the WO_LOG table.'
				end
			end else
				ErrorMsg  = 'Error in ':Service:' service. Failed to read record ':WONo:' from the WO_LOG table. '
				ErrorMsg := 'Error message: ':Error_Services('GetMessage')
			end
		end else
			ErrorMsg = 'Error in ':Service:' service. ':WONo:' does not exist in the WO_LOG table.'
		end
	end else
		ErrorMsg = 'Error in ':Service:' service. Null WONo passed into service'
	end
    
    LogData    = ''
    LogData<1> = LoggingDtm
    LogData<2> = WONo
    If ErrorMsg EQ '' then
    	LogResult   = 'Successfully updated WO_LOG record ':WONo:' field SCRAP_QTY_STATIC '
    	LogResult  := 'with scrapped quantity ':ScrapQty:'.'
    	LogData<3>  = LogResult
    	Logging_Services('AppendLog', objUpScrapLog, LogData, @RM, @FM)
    end else
    	LogResult   = 'Failed to update WO_LOG record ':WONo:' field SCRAP_QTY_STATIC with scrapped quantity '
    	LogResult  := ScrapQty:'. Error message: ':ErrorMsg
    	LogData<3>  = LogResult
    	Logging_Services('AppendLog', objUpScrapLog, LogData, @RM, @FM)
    	Error_Services('Add', ErrorMsg)
    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


Service RemoveWoMatCassetteFromWO(WoMatKey, Username)
	
	ErrorMessage = ''
	If RowExists('WO_MAT', WoMatKey) then
		WoNo = Field(WoMatKey, '*', 1)
		WOLogRecord = Database_Services('ReadDataRow', 'WO_LOG', WoNo, True$, 0, False$)
		If Error_Services('NoError') then
			WoMatKeys = WOLogRecord<WO_LOG_WO_MAT_KEY$>
			Locate WoMatKey in WoMatKeys using @VM setting CassPos then
				WoLogRecord<WO_LOG_WO_MAT_KEY$> = Delete(WOLogRecord<WO_LOG_WO_MAT_KEY$>, 1, CassPos, 0)
				Database_Services('WriteDataRow', 'WO_LOG', WoNo, WoLogRecord)
				If Error_Services('NoError') then
					Work_Order_Services('UpdateReceivedQty', WONo)
					Work_Order_Services('UpdateReleasedQty', WONo)
					Work_Order_Services('UpdateUnscheduledQuantities')
				end else
					ErrorMessage = Error_Services('GetMessage')
				end
			end else
				ErrorMessage = "Unable to locate cass no " : WoMatKey : ' in the WO_LOG ' : WoNo : ' Record.'
			end
		end else
			ErrorMessage = Error_Services('GetMessage')
		end
	end else
		ErrorMessage = 'Invalid WoMat Key ' : WoMatKey : ' passed to RemoveWoMatCassetteFromWO routine.'
	end
	If ErrorMessage NE '' then
		Error_Services('Add', ErrorMessage)
	end
	
end service

Service CreateVoidedLotRecord(LotId, LegacyLotId, LegacyLotType, WoMatKey, UserId)
    //Todo: Move to Lot_Services
    ErrorMessage = ''

    If LotId NE '' OR LegacyLotId NE '' then
        If LotId EQ '' then
            //Determine NG Lot id if none was passed to routine.
            if LegacyLotType NE '' then
                if LegacyLotType EQ 'RDS' OR LegacyLotType EQ 'WM_IN' OR LegacyLotType EQ 'WM_OUT' then
                    LotId = Lot_Services('GetLotIdByLegacyLotIdAndType', LegacyLotId, LegacyLotType)
                    If Error_Services('NoError') then
                        If LotID EQ '' then
                            //No Lot ID was found, lets create one.
                            LotId = Lot_Services('CreateNewLot', 'RDS', '', '', '', '', '', UserId, '', LegacyLotId)
                            If Error_Services('HasError') then
                                ErrorMessage = Error_Services('GetMessage')
                            end
                        end
                    end else
                        ErrorMessage = Error_Services('GetMessage')
                    end
                end else
                   ErrorMessage = 'Invalid Legacy Lot Type passed to routine.' 
                end
            end else
                ErrorMessage = 'A Legacy lot ID was passed to the routine, but the legacy lot type was null.'
            end
        end
        
        If ErrorMessage NE '' then
            If RowExists('LOT', LotId) then
                NewVoidedLotId = RTI_CreateGUID()
                NewVoidedLotRec = ''
                NewVoidedLotRec<VOIDED_LOT_WORK_ORDER_LOG_ID$> = Field(WoMatKey, '*', 1)
                NewVoidedLotRec<VOIDED_LOT_VOID_BY$> = UserId
                NewVoidedLotRec<VOIDED_LOT_VOID_DTM$> = Datetime()
                NewVoidedLotRec<VOIDED_LOT_ENTITY_TYPE$> = LegacyLotType
                NewVoidedLotRec<VOIDED_LOT_LOT_ID$> = LotId
                NewVoidedLotRec<VOIDED_LOT_LEGACY_LOT_ID$> = LegacyLotId
                NewVoidedLotRec<VOIDED_LOT_WO_MAT_KEY$> = WoMatKey
                Database_Services('WriteDataRow', 'VOIDED_LOT', NewVoidedLotId, NewVoidedLotRec)
                If Error_Services('HasError') then
                    ErrorMessage = Error_Services('GetMessage')
                end
            end else
                ErrorMessage = 'Unable to find lot record.'
            end
        end
    end else
        ErrorMessage = 'Both the lot id and the legacy lot id were null.'
    end
    
    If ErrorMessage NE '' then
        Error_Services('Add', ErrorMessage)
    end
    
end service

Service IsLotVoided(LotId)
    
    //Todo: Move to Lot_Services
    ErrorMessage = ''
    VoidLotRecords = ''
    Open 'DICT.VOIDED_LOT' to hVoidTable then
        
        SearchString = 'LOT_ID':@VM:LotId:@FM
        Btree.Extract(SearchString, 'VOIDED_LOTS', hVoidTable, VoidLotRecord)
    end else
        ErrorMessage = 'Unable to open VOIDED_LOT table.'
    end
    
    If ErrorMessage NE '' then
        Error_Services('Add', ErrorMessage)
    end
    
    Response = VoidLotRecords
    
end service

Service SignVoidNonEpp(WOMatKeys, WONo, Username)
	
	ErrorMessage = ''
    
	WOLogRecord = Database_Services('ReadDataRow', 'WO_LOG', WONo, True$, 0, False$)
	if Error_Services('NoError') then
        for each WoMatKey in WOMatKeys using @VM
            if WoMatKey NE '' then
                CassNo = Field(WoMatKey, '*', 2)
                ReactType = XLATE('WO_LOG', WONo, WO_LOG_REACT_TYPE$, 'X')
                If ReactType EQ 'ASM' OR ReactType EQ 'ASM+' OR ReactType EQ 'HTR' then
                    RDSNo  = Database_Services('ReadDataColumn', 'WO_MAT', WoMatKey, WO_MAT_RDS_NO$, True$, 0, False$)
                    If Error_Services('NoError') then
                        If WoLogRecord<WO_LOG_WO_MAT_KEY$> NE '' then
                            Locate WoMatKey in WoLogRecord<WO_LOG_WO_MAT_KEY$> using @VM setting CassPos then
                                WoLogRecord<WO_LOG_WO_MAT_KEY$> = Delete(WOLogRecord<WO_LOG_WO_MAT_KEY$>, 1, CassPos, 0)
                                WO_Mat_Services('SetWoMatVoidFlag', WOMatKey, Username)
                                if Error_Services('NoError') then
                                    RDS_Services('DetachRDSFromWO', RDSNo)
                                    If Error_Services('NoError') then
                                        //Create a voided LOT record then record an event.
                                        Work_Order_Services('CreateVoidedLotRecord', '', RDSNo, 'RDS', WoMatKey, Username)
                                        Lot_Event_Services('CreateLotEvent', RDSNo, DateTime, 'VOID', 'Lot voided by ' : Username, '', Username, True$, 'RDS')
                                    end else
                                        ErrorMessage = Error_Services('GetMessage')
                                    end
                                end else
                                    ErrorMessage = Error_Services('GetMessage')
                                end
                            end else
                                ErrorMessage = 'WO_MAT key not found in WO_LOG record.'
                            end
                        end else
                            ErrorMessage = 'No WO_MAT keys exist in WO_LOG record.'
                        end
                    end else
                        ErrorMessage = Error_Services('GetMessage')
                    end
                end else
                    ErrorMessage = 'Cannot use this routine to void EpiPro lots.'
                end
            end
        Next WoMatKey
    end else
        ErrorMessage = Error_Services('GetMessage')
    end

	If ErrorMessage EQ '' then
	    //Commit work order changes all at once here.
	    Database_Services('WriteDataRow', 'WO_LOG', WONo, WoLogRecord)
	    if Error_Services('NoError') then
	        Work_Order_Services('UpdateReceivedQty', WONo)
            Work_Order_Services('UpdateReleasedQty', WONo)
            Work_Order_Services('UpdateUnscheduledQuantities')
	    end else
	        Error_Services('Add', ErrorMessage)
	    end
    end else
        Error_Services('Add', ErrorMessage)
    end

end service


Service SignVoidWMI(WMInKeys, WONo, Username)
	
	ErrorMessage = ''
    
	WOLogRecord = Database_Services('ReadDataRow', 'WO_LOG', WONo, True$, 0, False$)
	if Error_Services('NoError') then
        for each WmInKey in WMInKeys using @VM
            if WmInKey NE '' then
                WONo   = Field(WMInKey, '*', 1)
                CassNo = Field(WMInKey, '*', 3)
                WoStepKey = Xlate('WO_LOG', WONo, WO_LOG_WO_STEP_KEY$, 'X')
                WoMatKey = WONo : '*' : CassNo
                ReactType = WOLogRecord<WO_LOG_REACT_TYPE$>
                If ReactType EQ 'EPP' then
                    WMInRec = Database_Services('ReadDataRow', 'WM_IN', WMInKey, True$, 0, False$)
                    IsVoided = WMInRec<WM_IN_VOID$>
                    If Not(IsVoided) then
                        IsOnHold = Hold_Services('CheckForHold', WoMatKey)
                        If Not(IsOnHold) then
                            WM_In_Services('SetVoidFlag', WmInKey, Username)
                            If Error_Services('NoError') then
                                Lot_Event_Services('CreateLotEvent', WmInKey, DateTime(), 'VOID', 'Lot voided by ' : Username, '', Username, True$, 'WM_IN')
                                Work_Order_Services('CreateVoidedLotRecord', '', WmInKey, 'WM_IN', WoMatKey, Username)
                                if Error_Services('NoError') then
                                    //Check if WMO is also voided. If it is, remove it from the WO_MAT_KEY field in WO_LOG, and void the WO_MAT record
                                    WMOKey = WMInKey; //the paired WMO key is the same as the WMIKey
                                    IsWMOVoided = Database_Services('ReadDataColumn', 'WM_OUT', WMOKey, WM_OUT_VOID$)
                                    If IsWMOVoided then
                                        Locate WoMatKey in WoLogRecord<WO_LOG_WO_MAT_KEY$> using @VM setting CassPos then
                                            WoLogRecord<WO_LOG_WO_MAT_KEY$> = Delete(WOLogRecord<WO_LOG_WO_MAT_KEY$>, 1, CassPos, 0)
                                            WO_Mat_Services('SetWoMatVoidFlag', WoMatKey, Username)
                                        end else
                                            ErrorMessage = 'Unable to locate WO_MAT key in WO_LOG record.'
                                        end
                                    end 
                                end else
                                    ErrorMessage = Error_Services('GetMessage')
                                end
                            end else
                                ErrorMessage = Error_Services('GetMessage')
                            end 
                        end
                    end else
                        ErrorMessage = 'Unable to void lot as it is already voided.'
                    end
                end else
                    ErrorMessage = 'Cannot use this routine to void non EpiPro lots.'
                end
            end
        Next WoMatKey
    end else
        ErrorMessage = Error_Services('GetMessage')
    end

	If ErrorMessage EQ '' then
	    //Commit work order changes all at once here.
	    Database_Services('WriteDataRow', 'WO_LOG', WONo, WoLogRecord)
	    if Error_Services('NoError') then
	        Work_Order_Services('UpdateReceivedQty', WONo)
            Work_Order_Services('UpdateReleasedQty', WONo)
            Work_Order_Services('UpdateUnscheduledQuantities')
	    end else
	        Error_Services('Add', ErrorMessage)
	    end
    end else
        Error_Services('Add', ErrorMessage)
    end
    
    If ErrorMessage NE '' then
		LogData    = ''
		LogData<1> = LoggingDTM
		LogData<2> = Username
		LogData<3> = WoNo
		LogData<4> = ErrorMessage
		Logging_Services('AppendLog', objReleaseLog, LogData, @RM, @FM, False$)
    	Error_Services('Add', ErrorMessage)
    end else
    	LogData    = ''
		LogData<1> = LoggingDTM
		LogData<2> = Username
		LogData<3> = WoNo
		LogData<4> = 'Void queued successfully.'
		Logging_Services('AppendLog', objReleaseLog, LogData, @RM, @FM, False$)
    	Error_Services('Add', ErrorMessage)
    end
    
end service


Service SignVoidWMO(WMOutKeys, WONo, Username)
    
	ErrorMessage = ''
    
	WOLogRecord = Database_Services('ReadDataRow', 'WO_LOG', WONo, True$, 0, False$)
	if Error_Services('NoError') then
        for each WmOutKey in WMOutKeys using @VM
            if WmOutKey NE '' then
                WONo   = Field(WmOutKey, '*', 1)
                CassNo = Field(WmOutKey, '*', 3)
                WoStepKey = Xlate('WO_LOG', WONo, WO_LOG_WO_STEP_KEY$, 'X')
                WoMatKey = WONo : '*' : CassNo
                ReactType = WOLogRecord<WO_LOG_REACT_TYPE$>
                If ReactType EQ 'EPP' then
                    WMOutRec = Database_Services('ReadDataRow', 'WM_OUT', WmOutKey, True$, 0, False$)
                    IsVoided = WMInRec<WM_OUT_VOID$>
                    If Not(IsVoided) then
                        IsOnHold = Hold_Services('CheckForHold', WoMatKey)
                        If Not(IsOnHold) then
                            WM_Out_Services('SetVoidFlag', WmOutKey, Username)

                            If Error_Services('NoError') then
                                If Error_Services('NoError') then
                                    If Error_Services('NoError') then
                                        Lot_Event_Services('CreateLotEvent', WmOutKey, DateTime(), 'VOID', 'Lot voided by ' : Username, '', Username, True$, 'WM_OUT')
                                        Work_Order_Services('CreateVoidedLotRecord', '', WmOutKey, 'WM_OUT', WoMatKey, Username)
                                        if Error_Services('NoError') then
                                            //Check if WMI is also voided. If it is, remove it from the WO_MAT_KEY field in WO_LOG, and void the WO_MAT record
                                            WMIKey = WmOutKey; //the paired WMO key is the same as the WMIKey
                                            IsWMIVoided = Database_Services('ReadDataColumn', 'WM_IN', WMOKey, WM_IN_VOID$)
                                            If IsWMIVoided then
                                                Locate WoMatKey in WoLogRecord<WO_LOG_WO_MAT_KEY$> using @VM setting CassPos then
                                                    WoLogRecord<WO_LOG_WO_MAT_KEY$> = Delete(WOLogRecord<WO_LOG_WO_MAT_KEY$>, 1, CassPos, 0)
                                                    WO_Mat_Services('SetWoMatVoidFlag', WoMatKey, Username)
                                                end else
                                                    ErrorMessage = 'Unable to locate WO_MAT key in WO_LOG record.'
                                                end
                                            end 
                                        end else
                                            ErrorMessage = Error_Services('GetMessage')
                                        end
                                    end else
                                        ErrorMessage = Error_Services('GetMessage')
                                    end
                                end else
                                    ErrorMessage = Error_Services('GetMessage')
                                end
                                
                            end else
                                ErrorMessage = Error_Services('GetMessage')
                            end
                        end
                    end else
                        ErrorMessage = 'Unable to void lot as it is already voided.'
                    end
                end else
                    ErrorMessage = 'Cannot use this routine to void non EpiPro lots.'
                end
            end
        Next WoMatKey
    end else
        ErrorMessage = Error_Services('GetMessage')
    end

	If ErrorMessage EQ '' then
	    //Commit work order changes all at once here.
	    Database_Services('WriteDataRow', 'WO_LOG', WONo, WoLogRecord)
	    if Error_Services('NoError') then
	        Work_Order_Services('UpdateReceivedQty', WONo)
            Work_Order_Services('UpdateReleasedQty', WONo)
            Work_Order_Services('UpdateUnscheduledQuantities')
	    end else
	        Error_Services('Add', ErrorMessage)
	    end
    end else
        Error_Services('Add', ErrorMessage)
    end
    
    If ErrorMessage NE '' then
		LogData    = ''
		LogData<1> = LoggingDTM
		LogData<2> = Username
		LogData<3> = WONo
		LogData<4> = ErrorMessage
		Logging_Services('AppendLog', objReleaseLog, LogData, @RM, @FM, False$)
    	Error_Services('Add', ErrorMessage)
    end else
    	LogData    = ''
		LogData<1> = LoggingDTM
		LogData<2> = Username
		LogData<3> = ''
		LogData<4> = 'Void processed successfully.'
		Logging_Services('AppendLog', objReleaseLog, LogData, @RM, @FM, False$)
    	Error_Services('Add', ErrorMessage)
    end
    
end service

Service GetEligiblePeelOffLotsByWOAndEntityType(WONo, EntityType)

    ErrorMessage = ''
    EligibleCassIds = ''
    
    If RowExists('WO_LOG', WONo) then
        Begin Case
            Case EntityType EQ 'RDS'
                CassIds = Database_Services('ReadDataColumn', 'WO_LOG', WONo, WO_LOG_WO_MAT_KEY$, True$, 0, False$)
                for each CassId in CassIds using @VM
                   RDSNo = XLATE('WO_MAT', CassId, WO_MAT_RDS_NO$, 'X')
                   Signatures = Signature_Services('GetSigProfile', CassId, 0, RDSNo)
                   Eligible = True$
                   for each Signature in Signatures<1> using @VM setting SigPos
                       SignatureDtm = Signatures<3, SigPos>
                       If SignatureDtm NE '' then
                           Eligible = False$
                       end
                   Until Eligible EQ False$
                   Next Signature
                   If Eligible then EligibleCassIds<1, -1> = CassId
                Next CassId
            Case EntityType EQ 'WM_OUT'
                //WM_OUTS can be voided when
                //1. There are no RDS Nos associated
                //2. There have never been any wafers in them
                WOStepKey = XLATE('WO_LOG', WONo, WO_LOG_WO_STEP_KEY$, 'X')
                CassIds = Database_Services('ReadDataColumn', 'WO_STEP', WOStepKey, WO_STEP_WM_OUT_KEYS$, True$, 0, False$)
                for each CassId in CassIds using @VM
                    WMORec = Database_Services('ReadDataRow', 'WM_OUT', CassId, True$, 0, False);//XLATE('WM_OUT',WMOutKeys<1,I>,'','X')
                    WMO_RdsNos		= WMORec<WM_OUT_RDS$>
                    WMO_NCRNos		= WMORec<WM_OUT_SLOT_NCR$>
                    WMO_UMWCassIDs	= WMORec<WM_OUT_UMW_CASS_ID$>
                    Eligible = True$
                    //Loop through RDS list to see if there is an RDS attached. If there are any RDS No's attached it is not eligible.
                    for each RDSNo in WMO_RDSNos using @VM
                        If RDSNo NE '' then
                            Eligible = False$
                        end
                    Until Eligible EQ False$    
                    Next RDSNo
                    //Loop through NCR list to see if there are any NCR's associated. If there are any NCR No's attached it is not eligible.
                    If Eligible then
                        For Each NCRNo in WMO_NCRNos using @VM
                            If NCRNo NE '' then
                                Eligible = False$
                            end
                        Until Eligible EQ False$
                        Next NCR
                    end
                    //Loop through MU Wafer Usage list to see if there are any MU wafer's taken from this lot. If there are any Makeup Wafers attached it is not eligible.
                    If Eligible then
                        For Each UMWCass in WMO_UMWCassIDs using @VM
                            If UMWCass NE '' then
                                Eligible = False$
                            end
                        Until Eligible EQ False$
                        Next UMWCass
                    end
                   If Eligible then EligibleCassIds<1, -1> = CassId
                Next CassId
            Case EntityType EQ 'WM_IN'
                WOStepKey = XLATE('WO_LOG', WONo, WO_LOG_WO_STEP_KEY$, 'X')
                CassIds = Database_Services('ReadDataColumn', 'WO_STEP', WOStepKey, WO_STEP_WM_IN_KEYS$, True$, 0, False$)
                for each CassId in CassIds using @VM
                    Eligible = True$
                    WMIRec = Database_Services('ReadDataRow', 'WM_IN', CassId, True$, 0, False);//XLATE('WM_OUT',WMOutKeys<1,I>,'','X')
                    WMI_RdsNos		= WMIRec<WM_IN_RDS_NO$>
                    WMI_NCRNos		= WMIRec<WM_IN_NCR_NO$>
                    Eligible = True$
                    //Loop through RDS list to see if there is an RDS attached. If there are any RDS No's attached it is not eligible.
                    for each RDSNo in WMI_RDSNos using @VM
                        If RDSNo NE '' then
                            Eligible = False$
                        end
                    Until Eligible EQ False$    
                    Next RDSNo
                    //Loop through NCR list to see if there are any NCR's associated. If there are any NCR No's attached it is not eligible.
                    If Eligible then
                        For Each NCRNo in WMI_NCRNos using @VM
                            If NCRNo NE '' then
                                Eligible = False$
                            end
                        Until Eligible EQ False$
                        Next NCR
                    end
                   If Eligible then EligibleCassIds<1, -1> = CassId
                Next CassId
            Case EntityType EQ ''
                ErrorMessage = 'Entity type parameter was invalid.'
            Case Otherwise$
                ErrorMessage = 'Entity type parameter was null.'
        End Case
    end else
        ErrorMessage = 'WO# ' : WONo : ' not found in WO_LOG table.'
    end
    If ErrorMessage NE '' then
        Error_Services('Add', 'Error in ' : Service : '. ' : ErrorMessage)
    end
    
    Response = EligibleCassIds
    
end service


Service UpdateOpenWorkOrderStatuses()
    
	hSysLists = Database_Services('GetTableHandle', 'SYSLISTS')
	Lock hSysLists, ServiceKeyID then
	    
	    StatusName = Service
        IsProd     = Environment_Services('IsProd')
        If IsProd then
            MonaResource = 'GRP_OPENINSIGHT_MES_OP_FE_SERVICE_MANAGER'
        end else
            MonaResource = 'GRP_OPENINSIGHT_MES_OP_FE_DEV_SERVICE_MANAGER'
        end	    
	    
        Server       = Environment_Services('GetServer')
        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] : ' Update Work Order Status Log.csv'
        Headers      = 'Logging DTM' : @FM : 'Work Order No' : @FM : 'Log'
        objStatusLog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$,',', Headers, '', False$, False$)    
        
        LogData     = ''
        LogData<1>  = OConv(Datetime(), 'DT/^S')
        LogData<2>  = Server
        LogData<3>  = 'Begin ':Service
        Logging_Services('AppendLog', objStatusLog, LogData, @RM, @FM)					    
        
        ErrorMsg      = ''
        OpenWoLogKeys = ''
        ErrCode       = ''
        Extract_Si_Keys('WO_LOG', 'CLOSE_DATE', '', OpenWoLogKeys)
        If Not(Get_Status(ErrCode)) then 
            If OpenWoLogKeys NE '' then
                For each OpenWoLogKey in OpenWoLogKeys using @VM
                    Service_Services('PostProcedure', 'WORK_ORDER_SERVICES', 'UpdateWorkOrderStatus':@VM:OpenWoLogKey)
                    If Error_Services('NoError') then
                        LogData<1> = OConv(Datetime(), 'DT/^S')
                        LogData<2> = Server
                        LogData<3> = 'Successfully posted UpdateWorkOrderStatus procedure to PROC_QUEUE for WO_LOG ':OpenWOLogKey:'.'
                        Logging_Services('AppendLog', objStatusLog, LogData, @RM, @FM)                           
                    end else
                        ErrorMsg   = 'Error posting UpdateWorkOrderStatus procedure to PROC_QUEUE for WO_LOG ':OpenWOLogKey:'.'
                        LogData<1> = OConv(Datetime(), 'DT/^S')
                        LogData<2> = Server
                        LogData<3> = ErrorMsg
                        Logging_Services('AppendLog', objStatusLog, LogData, @RM, @FM)                           
                    end
                Next OpenWoLogKey
            end else
                LogData<1> = OConv(Datetime(), 'DT/^S')
                LogData<2> = Server
                LogData<3> = 'No open work orders to update.'
                Logging_Services('AppendLog', objStatusLog, LogData, @RM, @FM)	
            end
        end else
            ErrorMsg   = 'Error calling Extract_Si_Keys. Error code: ':ErrCode:'.'
            LogData<1> = OConv(Datetime(), 'DT/^S')
            LogData<2> = Server
            LogData<3> = ErrorMsg
            Logging_Services('AppendLog', objStatusLog, LogData, @RM, @FM)        
        end
        
        LogData     = ''
        LogData<1>  = OConv(Datetime(), 'DT/^S')
        LogData<2>  = Server
        LogData<3>  = 'End ':Service
        Logging_Services('AppendLog', objStatusLog, LogData, @RM, @FM)
        
        If ErrorMsg EQ '' then
            MonaStatus = 'ok'
        end else
            MonaStatus = 'critical'
        end
        
        Mona_Services('PostStatus', MonaResource, StatusName, MonaStatus)
        
        Unlock hSysLists, ServiceKeyID else Null
    end
    
end service


Service UpdateWorkOrderStatus(WOLogId)
    
    StatusName = Service
    IsProd     = Environment_Services('IsProd')
    If IsProd then
        MonaResource = 'GRP_OPENINSIGHT_MES_OP_FE_SERVICE_MANAGER'
    end else
        MonaResource = 'GRP_OPENINSIGHT_MES_OP_FE_DEV_SERVICE_MANAGER'
    end	    
    
    Server       = Environment_Services('GetServer')
    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] : ' Update Work Order Status Log.csv'
    Headers      = 'Logging DTM' : @FM : 'Work Order No' : @FM : 'Log'
    objStatusLog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$,',', Headers, '', False$, False$)    
    
    LogData     = ''
    LogData<1>  = OConv(Datetime(), 'DT/^S')
    LogData<2>  = Server
    LogData<3>  = 'Begin ':Service
    Logging_Services('AppendLog', objStatusLog, LogData, @RM, @FM)	    
    
    ErrorMsg = ''
    If WOLogID NE '' then
        If RowExists('WO_LOG', WOLogId) then
            WoLogRec = Database_Services('ReadDataRow', 'WO_LOG', WOLogId)
            If Error_Services('NoError') then 
                CurrStatus   = WoLogRec<WO_LOG_CURR_STATUS_STATIC$>
                NewStatus    = obj_WO_Log('CurrStatus', WOLogId:@RM:WoLogRec)
                If CurrStatus NE NewStatus then
                    LogData<1> = OConv(Datetime(), 'DT/^S')
                    LogData<2> = Server
                    LogData<3> = 'Status change from ':CurrStatus:' to ':NewStatus:' for WO_LOG ':WOLogId:'.'
                    Logging_Services('AppendLog', objStatusLog, LogData, @RM, @FM)	                        
                    Transaction_Services('PostWriteFieldTransaction', 'WO_LOG', WOLogId, WO_LOG_CURR_STATUS_STATIC$, NewStatus)
                    If Error_Services('NoError') then
                        LogData<1> = OConv(Datetime(), 'DT/^S')
                        LogData<2> = Server
                        LogData<3> = 'Successfully posted status change for WO_LOG ':WOLogId:' to the TRANSACTION_QUEUE.'
                        Logging_Services('AppendLog', objStatusLog, LogData, @RM, @FM)                            
                    end else
                        ErrorMsg   = 'Error posting status change for WO_LOG ':WOLogId:' to the TRANSACTION_QUEUE.'
                        LogData<1> = OConv(Datetime(), 'DT/^S')
                        LogData<2> = Server
                        LogData<3> = ErrorMsg
                        Logging_Services('AppendLog', objStatusLog, LogData, @RM, @FM)                                    
                    end                    
                end else
                    LogData<1> = OConv(Datetime(), 'DT/^S')
                    LogData<2> = Server
                    LogData<3> = 'No status change for WO_LOG ':WOLogId:'.'
                    Logging_Services('AppendLog', objStatusLog, LogData, @RM, @FM)	                    
                end
            end else
                ErrorMsg   = 'Error in ':Service:' service. Error reading WO_LOG ':WOLogId:' record. Error message: ':Error_Services('GetMessage')
                LogData<1> = OConv(Datetime(), 'DT/^S')
                LogData<2> = Server
                LogData<3> = ErrorMsg
                Logging_Services('AppendLog', objStatusLog, LogData, @RM, @FM)                 
            end
        end else
            ErrorMsg   = 'Error in ':Service:' service. WO_LOG ':WOLogId:' does not exist.'
            LogData<1> = OConv(Datetime(), 'DT/^S')
            LogData<2> = Server
            LogData<3> = ErrorMsg
            Logging_Services('AppendLog', objStatusLog, LogData, @RM, @FM) 
        end
    end else
        ErrorMsg   = 'Error in ':Service:' service. Null WOLogId passed into service.'
        LogData<1> = OConv(Datetime(), 'DT/^S')
        LogData<2> = Server
        LogData<3> = ErrorMsg
        Logging_Services('AppendLog', objStatusLog, LogData, @RM, @FM) 
    end
    
    LogData    = ''
    LogData<1> = OConv(Datetime(), 'DT/^S')
    LogData<2> = Server
    LogData<3> = 'End ':Service
    Logging_Services('AppendLog', objStatusLog, LogData, @RM, @FM)
    
    If ErrorMsg EQ '' then
        MonaStatus = 'ok'
    end else
        MonaStatus = 'critical'
    end
    
    Mona_Services('PostStatus', MonaResource, StatusName, MonaStatus)    
    
    If ErrorMsg EQ '' then
        Response = True$
    end else
        Response = False$
        Error_Services('Add', ErrorMsg)
    end
    
end service

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

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


