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

    Name        :   WM_Out_Services

    Description :   Handler program for all WM_OUT services.

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

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

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

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

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

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

    Metadata    :

    History     :   (Date, Initials, Notes)
        11/16/22    djs     Original programmer.
        08/31/23	djm		Added view/add comment functions.

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

$Insert SERVICE_SETUP
$Insert APP_INSERTS
$Insert MSG_EQUATES
$Insert DICT_EQUATES
$Insert WM_OUT_EQUATES
$Insert WO_MAT_EQUATES
$Insert WO_LOG_EQUATES
$Insert COMPANY_EQUATES
$Insert RETURN_TO_FAB_LOTS_EQUATES
$Insert PROD_SPEC_EQUATES
$Insert WO_STEP_EQUATES
$Insert EPI_PART_EQUATES
$Insert CUST_EPI_PART_EQUATES
$Insert PRS_STAGE_EQUATES
$Insert UNIT_EQUATES

Equ NUM_ATTEMPTS$ to 60

Declare function   Database_Services, SRP_JSON, Error_Services, Clean_Insp_Services, WO_Mat_QA_Services
Declare function   PSN_Services, SRP_Rotate_Array, Datetime, Return_To_Fab_Services, Environment_Services
Declare function   Logging_Services
Declare subroutine Database_Services, SRP_JSON, Error_Services, Extract_Si_Keys, Set_Status, obj_wo_mat_log
Declare subroutine Logging_Services, Btree.Extract, Update_Index, Delay

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

Return Response else ''

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


//----------------------------------------------------------------------------------------------------------------------
// GetComments
//
// WMOutNo - [Required]
//
// Returns a delimited array of all EPP_COMMENT_NOTE, EPP_COMMENT_USER, and EPP_COMMENT_DATE
// associated with a WM_OUT record. Note: Dates are returned Oconv'd.
//----------------------------------------------------------------------------------------------------------------------
Service GetComments(WMOutNo)
    
    CommentArray = ''
    WMOutRow          = Database_Services('ReadDataRow', 'WM_OUT', WMOutNo)
    
    CommentDates = Oconv(WMOutRow<WM_OUT_EPP_COMMENT_DATE$>, 'DT')
    CommentUsers = WMOutRow<WM_OUT_EPP_COMMENT_USER$>
    Comments = WMOutRow<WM_OUT_EPP_COMMENT_NOTE$>
    
    CommentList = CommentDates :@FM: CommentUsers :@FM: Comments
    CommentArray = SRP_Rotate_Array(CommentList)
    Response = CommentArray
    
End Service


//----------------------------------------------------------------------------------------------------------------------
// AddComment
//
// WMOutNo - [Required]
// Comment - [Required]
//
// Adds  an EPP_COMMENT_NOTE, EPP_COMMENT_USER, and EPP_COMMENT_DATE
// to a WM_OUT record.
//----------------------------------------------------------------------------------------------------------------------
Service AddComment(WMOutNo, Comment, UsernameOpt)
    
    WMOutRow          = Database_Services('ReadDataRow', 'WM_OUT', WMOutNo)
    UserName		= @USER4
    If Assigned(UsernameOpt) then
        If UsernameOpt NE '' then
            Username = UsernameOpt
        end
    end
    CommentTime		= Datetime()
    
    OldDates = WMOutRow<WM_OUT_EPP_COMMENT_DATE$>
    OldUsers = WMOutRow<WM_OUT_EPP_COMMENT_USER$>
    OldComments = WMOutRow<WM_OUT_EPP_COMMENT_NOTE$>
    If (OldDates EQ '' AND OldUsers EQ '' AND OldComments EQ '') then
        WMOutRow<WM_OUT_EPP_COMMENT_DATE$> = CommentTime
        WMOutRow<WM_OUT_EPP_COMMENT_USER$> = UserName
        WMOutRow<WM_OUT_EPP_COMMENT_NOTE$> = Comment
    end else
        WMOutRow<WM_OUT_EPP_COMMENT_DATE$> = CommentTime  :@VM: OldDates
        WMOutRow<WM_OUT_EPP_COMMENT_USER$> = UserName :@VM: OldUsers
        WMOutRow<WM_OUT_EPP_COMMENT_NOTE$> = Comment :@VM: OldComments
    end
    Database_Services('WriteDataRow', 'WM_OUT', WMOutNo, WMOutRow, 1, 0, 0)
    
End Service


Service ConvertRecordToJSON(KeyID, Record, ItemURL)
    
    jsonRecord    = ''
    
    If KeyID NE '' then
        
        If Record EQ '' then Record = Database_Services('ReadDataRow', 'WM_OUT', KeyID)
        If Error_Services('NoError') then
            @DICT   = Database_Services('GetTableHandle', 'DICT.WM_OUT')
            @ID     = KeyID
            @RECORD = Record
            objJSON = ''
            If SRP_JSON(objJSON, 'New', 'Object') then
                objWMOut = ''
                If SRP_JSON(objWMOut, 'New', 'Object') then 
                    SRP_JSON(objWMOut, 'SetValue', 'keyId', @ID)
                    SRP_JSON(objWMOut, 'SetValue', 'workOrder', {WO_NO})
                    SRP_JSON(objWMOut, 'SetValue', 'outCassNo', {OUT_CASS_NO})
                    WoMatKey            = {WO_NO} : '*' : {OUT_CASS_NO}
                    SAPBatchNo          = Database_Services('ReadDataColumn', 'WO_MAT', WoMatKey, WO_MAT_SAP_BATCH_NO$, True$, 0, False$)
                    SRP_JSON(objWMOut, 'SetValue', 'SapBatchNo', SAPBatchNo, 'String')
                    CurrWfrQty          = XLATE('WO_MAT', WoMatKey, 'CURR_WFR_CNT', 'X')
                    SRP_JSON(objWMOut, 'SetValue', 'CURR_WFR_CNT', CurrWfrQty)
                    CustNo              = Database_Services('ReadDataColumn', 'WO_LOG', {WO_NO}, WO_LOG_CUST_NO$, True$, 0, False$)
                    CustReshipNo = Database_Services('ReadDataColumn', 'WO_MAT', WoMatKey, WO_MAT_RESHIP_CUST_NO$, True$, 0, False$)
                    If CustReshipNo NE '' then
                        CustNo = CustReshipNo
                    end
                    CustName            = Database_Services('ReadDataColumn', 'COMPANY', CustNo, COMPANY_CO_NAME$, True$, 0, False$)
                    CustAbbrev          = Database_Services('ReadDataColumn', 'COMPANY', CustNo, COMPANY_ABBREV$, True$, 0, False$)
                    SRP_JSON(objWMOut, 'SetValue', 'CustNo', CustNo)
                    SRP_JSON(objWMOut, 'SetValue', 'CustName', CustName)
                    SRP_JSON(objWMOut, 'SetValue', 'CustAbbrev', CustAbbrev)
                    SRP_JSON(objWMOut, 'SetValue', 'PSN', {PS_NO})
                    SRP_JSON(objWMOut, 'SetValue', 'partNo', {PART_NO})
                    SRP_JSON(objWMOut, 'SetValue', 'postEpiSig', {POST_EPI_SIG})
                    SRP_JSON(objWMOut, 'SetValue', 'postEpiSigDtm', OConv({POST_EPI_SIG_DTM}, 'DT/^1HS'))
                    SRP_JSON(objWMOut, 'SetValue', 'supVerSig', {SUP_VER_SIG})
                    SRP_JSON(objWMOut, 'SetValue', 'supVerSigDtm', OConv({SUP_VER_SIG_DTM}, 'DT/^1HS'))
                    CINos               = {EPO_CI_NO}
                    // Add a PSN object
                    ProdSpecJSON        = PSN_Services('ConvertRecordToJSON', {PS_NO})
                    objProdSpecResponse = ''
                    If SRP_JSON(objProdSpecResponse, 'Parse', ProdSpecJSON) EQ '' then
                        objProdSpec = SRP_JSON(objProdSpecResponse, 'Get', 'prodSpec')
                        SRP_JSON(objWMOut, 'Set', 'prodSpec', objProdSpec)
                        SRP_JSON(objProdSpec, 'Release')
                        SRP_JSON(objProdSpecResponse, 'Release')
                    end
                    // Add CLEAN_INSP object(s)
                    If CINos NE '' then
                        objCleanInspArray = ''
                        If SRP_JSON(objCleanInspArray, 'New', 'Array') then
                            For each CINo in CINos using @VM setting vPos
                                If CINo NE '' then
                                    CleanInspJSON = Clean_Insp_Services('ConvertRecordToJSON', CINo)
                                    objCleanInsp  = ''
                                    If SRP_JSON(objCleanInsp, 'Parse', CleanInspJSON) EQ '' then
                                        objTemp = SRP_JSON(objCleanInsp, 'Get', 'cleanInsp')
                                        SRP_JSON(objCleanInspArray, 'Add', objTemp)
                                        SRP_JSON(objTemp, 'Release')
                                        SRP_JSON(objCleanInsp, 'Release')
                                    end
                                end
                            Next CINo
                            SRP_JSON(objWMOut, 'Set', 'cleanInsp', objCleanInspArray)
                            SRP_JSON(objCleanInspArray, 'Release')
                        end
                    end
                    
                    // Add WO_MAT_QA object
                    WOMatQAKey  = Field(KeyID, '*', 1):'*':Field(KeyID, '*', 3)
                    WOMatQAJSON = WO_Mat_QA_Services('ConvertRecordToJSON', WOMatQAKey)
                    objWOMatQA  = ''
                    If SRP_JSON(objWOMatQA, 'Parse', WOMatQAJSON) EQ '' then
                        objTemp = SRP_JSON(objWOMatQA, 'Get', 'woMatQa')
                        SRP_JSON(objWMOut, 'Set', 'woMatQA', objTemp)
                        SRP_JSON(objTemp, 'Release')
                        SRP_JSON(objWOMatQA, 'Release')
                    end
                    AllRTFRecords = Return_To_Fab_Services('GetReturnToFabRecordIdByCassId', KeyID)
                    If AllRTFRecords NE '' then
                        objRTFRecords = ''
                        If SRP_JSON(objRTFRecords, 'New', 'Array') then
                            For each RTFRecordId in AllRTFRecords using @VM setting vPos
                                objRTF = ''
                                If SRP_JSON(objRTF, 'New', 'Object') then
                                    RTFRecord = Database_Services('ReadDataRow', 'RETURN_TO_FAB_LOTS', RTFRecordId, True$, 0, False$)
                                    SRP_JSON(objRTF, 'SetValue', 'ReturnToFabLotsId', RTFRecordId)
                                    SRP_JSON(objRTF, 'SetValue', 'StartDtm', OConv(RTFRecord<RETURN_TO_FAB_LOTS_MH_INIT_DTM$>, 'DT'))
                                    SRP_JSON(objRTF, 'SetValue', 'Completed', RTFRecord<RETURN_TO_FAB_LOTS_COMPLETED$>, 'Boolean')
                                    SRP_JSON(objRTFRecords, 'Set', 'ReturnToFabRecord', objRTF)
                                    SRP_JSON(objRTFRecords, 'Add', objRTF)
                                    SRP_JSON(objRTF, 'Release')
                                end
                            Next RTFRecordId
                            
                            SRP_JSON(objWMOut, 'Set', 'ReturnToFabRecords', objRTFRecords)
                            SRP_JSON(objRTFRecords, 'Release')
                        end
                    end else
                        SRP_JSON(objWMOut, 'SetValue', 'ReturnToFabRecords', '')
                    end
                    SRP_JSON(objJSON, 'Set', 'wmOut', objWMOut)
                    SRP_JSON(objWMOut, '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


//----------------------------------------------------------------------------------------------------------------------
// GetWMOData
//
// WorkOrderNo. - [Required]
// KeysOnly.    - [Optional]
//
// Returns a list of WMO records within a supplied Work Order.
// Rows are @FM delimted while columns are @VM delimited.
//----------------------------------------------------------------------------------------------------------------------
Service GetWMOData(WorkOrderNo, Columns, ShowGasGauge, WMOOverrideList)
    
    WMOList   = ''
    
    If ( (WorkOrderNo NE '') or (WMOOverrideList NE '') ) then
        If ShowGasGauge NE True$ then ShowGasGauge = False$
        rv              = Set_Status(0)
        WMOKeys         = ''
        
        If WorkOrderNo NE '' then
            Extract_Si_Keys('WM_OUT', 'WO_NO', WorkOrderNo, WMOKeys)
        end else
            Swap @FM with @VM in WMOOverrideList
            WMOKeys = WMOOverrideList
        end
        StatusCode      = ''
        If Get_Status(StatusCode) then
            Error_Services('Add', 'Error calling Extract_SI_Keys in the ' : Service : ' service. StatusCode: ' : StatusCode)
        end else
            NumWMO = DCount(WMOKeys, @VM)
            
            If ShowGasGauge then
                Def = ""
                Def<MCAPTION$>   = "Loading Outbound Cassettes..."
                Def<MTYPE$>      = "G"
                Def<MEXTENT$>    = NumWMO
                Def<MTEXTWIDTH$> = 400
                Def<MCOL$>       = -2        ;* message h-pos in pixels, or -2 (center screen, the default), -1 (center parent)
                Def<MROW$>       = -2        ;* message v-pos in pixels
                MsgUp  = Msg(@Window, Def)
            end
            
            @DICT  = Database_Services('GetTableHandle', 'DICT.' : 'WM_OUT')
            If Error_Services('NoError') then
                For each @ID in WMOKeys using @VM setting fPos
                    @RECORD = Database_Services('ReadDataRow', 'WM_OUT', @ID)
                    If Error_Services('NoError') then
                        For each Column in Columns using @VM setting vPos
                            Begin Case
                                Case Column EQ 'HOLD'
                                    HoldStatus          = Calculate(Column)
                                    If HoldStatus EQ True$ then
                                        HoldStatus = 'On Hold'
                                    end else
                                        HoldStatus = 'Off Hold'
                                    end
                                    WMOList<fPos, vPos> = HoldStatus
                                Case Otherwise$
                                    Val  = Calculate(Column)
                                    Conv = Xlate('DICT.WM_OUT', Column, DICT_CONV$, 'X')
                                    If Conv NE '' then
                                        Val  = OConv(Val, Conv)
                                    end
                                    WMOList<fPos, vPos> = Val
                            End Case
                        Next Column
                    end else
                        Error_Services('Add', 'Error reading WM_OUT Record ' : @ID : ' in the ' : Service : ' service.')
                    end
                    * update the gauge
                    If ShowGasGauge then Msg(@Window, MsgUp, fPos, MSGINSTUPDATE$)
                Next @ID
            end else
                Error_Services('Add', 'Error opening WM_OUT dictionary in the ' : Service : ' service.')
            end
        end
    end else
        Error_Services('Add', 'WorkOrderNo or WMOOverrideList argument was missing from the ' : Service : ' service.')
    end
    If ShowGasGauge then Msg(@Window, MsgUp)         ;* take down the gauge
    Response    = WMOList
    
end service


//----------------------------------------------------------------------------------------------------------------------
// GetWaferMap
//
// Returns a @VM delimited array of boolean values, which represents which slots are filled.
// Note: The response is in the slot order 25, 24, ..., 2, 1.
//----------------------------------------------------------------------------------------------------------------------
Service GetWaferMap(WMOKey)
    
    WaferMap = ''
    ErrorMsg = ''
    If WMOKey NE '' then
        If RowExists('WM_OUT', WMOKey) then
            WOMatKey  = Xlate('WM_OUT', WMOKey, 'WO_MAT_KEY', 'X')
            ReactType = Xlate('WO_MAT', WOMatKey, 'REACTOR_TYPE', 'X')
            If ReactType EQ 'EPP' then 
                RDSNos   = Xlate('WM_OUT', WMOKey, 'RDS', 'X')
                WaferMap = ''
                For SlotIndex = 1 to 25
                    RDSNo    = RDSNos<0, SlotIndex>
                    WaferMap = Insert(WaferMap, 0, 1, 0, (RDSNo NE '') )
                Next SlotIndex
            end else
                ErrorMsg = 'Error in ':Service:' service. Non-EpiPro is not supported by this service. Use WO_MAT_SERVICES instead.'
            end
        end else
            ErrorMsg = 'Error in ':Service:' service. WM_OUT record ':WMOKey:' does not exist.'
        end
    end else
        ErrorMsg = 'Error in ':Service:' service. Null WMOKey passed in.'
    end
    
    If ErrorMsg EQ '' then
        Response = WaferMap
    end else
        Error_Services('Add', ErrorMsg)
        Response = False$
    end
    
end service

Service SetVoidFlag(WMOutKey, Username)
    ErrorMessage = ''
    If RowExists('WM_OUT', WMOutKey) then
        WMOutRec = Database_Services('ReadDataRow', 'WM_OUT', WMOutKey, True$, 0, False$)
        If Error_Services('NoError') then
            WMOutRec<WM_OUT_VOID$> = True$
            Database_Services('WriteDataRow', 'WM_OUT', WMOutKey, WMOutRec, True$, False, False$)
            If Error_Services('NoError') then    
                Set_Status(0)
                WONo = Field(WMOutKey, '*', 1)
                CassNo = Field(WMOutKey, '*', 3)
                WOMLParms = ''
                LogFile	= 'WO_MAT'					; WOMLParms  = LogFile:@RM
                LogDTM	= OConv(Datetime(), 'DT')	; WOMLParms := LogDTM:@RM
                Action	= 'WM_OUT_VOID'				; WOMLParms := Action:@RM
                WhCd	= 'CR'						; WOMLParms := WhCd:@RM
                LocCd	= 'VOID'					; WOMLParms := LocCd:@RM
                WONos	= WONo						; WOMLParms := WONos:@RM
                CassNos	= CassNo					; WOMLParms := CassNos:@RM
                UserID	= Username					; WOMLParms := UserID:@RM
                Tags	= ''						; WOMLParms := Tags:@RM
                ToolID	= ''						; WOMLParms := ToolID:@RM
                WOMLParms := ''
                obj_WO_Mat_Log('Create',WOMLParms)
                IF Get_Status(errCode) THEN
                    ErrorMessage = 'Error writing inventory transactions'
                end
            end else
                ErrorMessage = 'Failed to write to the WM_OUT record ' : WMOutKey : ' while attempting to set void                     status.'
            end
        end else
            ErrorMessage = 'Failed to read WM_OUT record ' : WMOutKey : ' while attempting to set void status.'
        end
    end else
        ErrorMessage = 'Invalid WM_OUT Key ' : WMOutKey : ' passed to SetVoidFlag routine.'
    end
    If ErrorMessage NE '' then
        Error_Services('Add', ErrorMessage)
    end
    
end service


Service VerifyWoStepWMOKeyIndex(WMOKey)
    
    LogPath            = Environment_Services('GetApplicationRootPath') : '\LogFiles\WM_OUT'
    LogDate            = Oconv(Date(), 'D4/')
    LogTime            = Oconv(Time(), 'MTS')
    LogFileName        = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' !WM_OUT WO_STEP{WM_OUT_KEYS} Log.csv'
    Headers            = 'Logging DTM':@FM:'WMOKey':@FM:'WOStep':@FM:'Result'
    objVerifyWMOKeyLog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, COMMA$, Headers, '', False$, False$)
    
    LogData    = ''
    LogData<1> = OConv(Datetime(), 'DT/^S')
    LogData<2> = WMOKey
    LogData<4> = 'Begin ':Service
    Logging_Services('AppendLog', objVerifyWMOKeyLog, LogData, @RM, @FM)
    
    ErrorMsg = ''
    If WMOKey NE '' then
        If RowExists('WM_OUT', WMOKey) then
            WOStepKey = Field(WMOKey, '*', 1, 2)
            If WOStepKey NE '' then
                WOStepWMOKeys = Xlate('WO_STEP', WOStepKey, 'WM_OUT_KEYS', 'X')
                LogData<1>    = OConv(Datetime(), 'DT/^S')
                LogData<3>    = WOStepKey
                Logging_Services('AppendLog', objVerifyWMOKeyLog, LogData, @RM, @FM)
                Locate WMOKey in WOStepWMOKeys using @VM setting vPos else
                    LogData<1>          = OConv(Datetime(), 'DT/^S')
                    LogData<4>          = 'WMOKey missing from WO_STEP record. Generating index transaction.'
                    Logging_Services('AppendLog', objVerifyWMOKeyLog, LogData, @RM, @FM)     
                    // Add index transaction to update WM_OUT_KEYS relational index (target WO_STEP table)
                    IndexTransactionRow = 'WO_STEP*WM_OUT_KEYS*AR':@FM:WMOKey:@FM:"":@FM:WOStepKey:@FM
                    Open "!WM_OUT" to BangTable then
                        Done = False$
                        For AttemptNo = 1 to NUM_ATTEMPTS$
                            If AttemptNo GT 1 then Delay(1)                        
                            Lock BangTable, 0 then
                                Read PendingTrans from BangTable, 0 else PendingTrans = '0':@FM
                                PendingTrans := IndexTransactionRow
                                Write PendingTrans on BangTable, 0 then
                                    LogData<1> = OConv(Datetime(), 'DT/^S')
                                    LogData<4> = 'Index transaction successfully added.'
                                    Logging_Services('AppendLog', objVerifyWMOKeyLog, LogData, @RM, @FM)
                                    Done       = True$
                                end else
                                    ErrorMsg = 'Unable to write index transaction to !WM_OUT. ':WMOKey
                                end
                                Unlock BangTable, 0 else ErrorMsg = 'Unable to Open !WM_OUT to add index transaction. ':WMOKey
                            end else
                                If AttemptNo GE NUM_ATTEMPTS$ then 
                                    ErrorMsg = 'Unable to Lock !WM_OUT to add index transaction. ':WMOKey
                                end
                            end
                        Until Done or ErrorMsg
                        Next AttemptNo
                    end else
                        ErrorMsg = 'Unable to Open !WM_OUT to add index transaction. ':WMOKey
                    end
                end
            end else
                LogData<1> = OConv(Datetime(), 'DT/^S')
                LogData<4> = 'WO_STEP key for WM_OUT ':WMOKey:' is null. Nothing to update.'
                Logging_Services('AppendLog', objVerifyWMOKeyLog, LogData, @RM, @FM)                    
            end
        end
    end
    
    If ErrorMsg NE '' then
        LogData<1> = OConv(Datetime(), 'DT/^S')
        LogData<4> = ErrorMsg
        Logging_Services('AppendLog', objVerifyWMOKeyLog, LogData, @RM, @FM)
    end
    
    LogData<1> = OConv(Datetime(), 'DT/^S')
    LogData<4> = 'End ':Service
    Logging_Services('AppendLog', objVerifyWMOKeyLog, LogData, @RM, @FM)
    
    If ErrorMsg NE '' then Error_Services('Add', ErrorMsg)    
    
end service

Service VerifyRelationalIndexes(WMOKey)
    
    LogPath            = Environment_Services('GetApplicationRootPath') : '\LogFiles\WM_OUT'
    LogDate            = Oconv(Date(), 'D4/')
    LogTime            = Oconv(Time(), 'MTS')
    LogFileName        = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' !WM_OUT WO_STEP{WM_OUT_KEYS} Log.csv'
    Headers            = 'Logging DTM':@FM:'WMOKey':@FM:'WOStep':@FM:'Result'
    objVerifyWMOKeyLog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, COMMA$, Headers, '', False$, False$)
    LoggingDTM         = LogDate : ' ' : LogTime   ; // Logging DTM    
    
    LogData    = ''
    LogData<1> = LoggingDtm
    LogData<2> = WMOKey
    LogData<4> = 'Begin ':Service
    Logging_Services('AppendLog', objVerifyWMOKeyLog, LogData, @RM, @FM)
    
    ErrorMsg = ''
    If WMOKey NE '' then
        
        WOStepKey = Field(WMOKey, '*', 1, 2)
        If WOStepKey NE '' then
            WOStepWMOKeys = Xlate('WO_STEP', WOStepKey, 'WM_OUT_KEYS', 'X')
            LogData<3>    = WOStepKey
            Logging_Services('AppendLog', objVerifyWMOKeyLog, LogData, @RM, @FM)
            
            LogData<4>          = 'WMOKey missing from WO_STEP record. Generating index transaction.'
            Logging_Services('AppendLog', objVerifyWMOKeyLog, LogData, @RM, @FM)     
            // Add index transaction to update WM_OUT_KEYS relational index (target WO_STEP table)
            
            If RowExists('WM_OUT', WMOKey) then 
                IndexTransactionRow = 'WO_STEP*WM_OUT_KEYS*AR':@FM:WMOKey:@FM:"":@FM:WOStepKey:@FM
            end else
                IndexTransactionRow = 'WO_STEP*WM_OUT_KEYS*AR':@FM:WMOKey:@FM:WOStepKey:@FM:"":@FM
            end
            Done = False$
            For AttemptNo = 1 to NUM_ATTEMPTS$
                If AttemptNo GT 1 then Delay(1)
                Open "!WM_OUT" to BangTable then
                    Lock BangTable, 0 then
                        Read PendingTrans from BangTable, 0 else PendingTrans = '0':@FM
                        PendingTrans := IndexTransactionRow
                        Write PendingTrans on BangTable, 0 then
                            Done = True$
                            LogData<4> = 'Index transaction successfully added.'
                            Logging_Services('AppendLog', objVerifyWMOKeyLog, LogData, @RM, @FM)     
                        end else
                            If AttemptNo GE NUM_ATTEMPTS$ then
                                ErrorMsg = 'Unable to write index transaction to !WM_OUT. ':WMOKey
                            end
                        end
                        Unlock BangTable, 0 else ErrorMsg = 'Unable to Open !WM_OUT to add index transaction. ':WMOKey
                    end else
                        If AttemptNo GE NUM_ATTEMPTS$ then
                            ErrorMsg = 'Unable to Lock !WM_OUT to add index transaction. ':WMOKey
                        end
                    end
                end else
                    If AttemptNo GE NUM_ATTEMPTS$ then
                        ErrorMsg = 'Unable to Open !WM_OUT to add index transaction. ':WMOKey
                    end
                end
            Until Done or ErrorMsg
            Next AttemptNo
            If Done then
                ErrCode = ''
                Update_Index('WM_OUT', 'WO_STEP_KEY', False$, True$)
                If Get_Status(ErrCode) then
                    ErrorMsg = 'Error in ':Service:' service. Update_Index call failed. Error code: ':ErrCode
                end
            end
        end else
            LogData<4> = 'WO_STEP key for WM_OUT ':WMOKey:' is null. Nothing to update.'
            Logging_Services('AppendLog', objVerifyWMOKeyLog, LogData, @RM, @FM)                    
        end
    end
    
    If ErrorMsg NE '' then
        LogData<4> = ErrorMsg
        Logging_Services('AppendLog', objVerifyWMOKeyLog, LogData, @RM, @FM)
    end
    
    LogData<4> = 'End ':Service
    Logging_Services('AppendLog', objVerifyWMOKeyLog, LogData, @RM, @FM)
    
    If ErrorMsg NE '' then Error_Services('Add', ErrorMsg)    
    
end service


Service VerifyWOLogWMOKeyIndex(WMOKey)
    
    LogPath                = Environment_Services('GetApplicationRootPath') : '\LogFiles\WM_OUT'
    LogDate                = Oconv(Date(), 'D4/')
    LogTime                = Oconv(Time(), 'MTS')
    LogFileName            = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' !WM_OUT WM_OUT{WO_NO} Log.csv'
    Headers                = 'Logging DTM':@FM:'WMOKey':@FM:'WOStep':@FM:'Result'
    objVerifyWMOWoIndexLog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, COMMA$, Headers, '', False$, False$)
    
    LogData    = ''
    LogData<1> = OConv(Datetime(), 'DT/^S')
    LogData<2> = WMOKey
    LogData<4> = 'Begin ':Service
    Logging_Services('AppendLog', objVerifyWMOWoIndexLog, LogData, @RM, @FM)
    
    ErrorMsg = ''
    If WMOKey NE '' then
        If RowExists('WM_OUT', WMOKey) then
            WONo = Field(WMOKey, '*', 1, 1)
            If WONo NE '' then
                WOLogRDSKeys = ''
                Extract_Si_Keys('WM_OUT', 'WO_NO', WONo, WOLogRDSKeys)
                LogData<1>   = OConv(Datetime(), 'DT/^S')
                LogData<3>   = WONo
                Logging_Services('AppendLog', objVerifyWMOWoIndexLog, LogData, @RM, @FM)
                Locate WMOKey in WOLogRDSKeys using @VM setting vPos else
                    LogData<4>          = 'WMOKey missing from Btree index. Generating index transaction.'
                    Logging_Services('AppendLog', objVerifyWMOWoIndexLog, LogData, @RM, @FM)     
                    // Add index transaction to update WO_NO btree index
                    IndexTransactionRow = 'WO_NO':@FM:WMOKey:@FM:"":@FM:WONo:@FM
                    Open "!WM_OUT" to BangTable then
                        Done = False$
                        For AttemptNo = 1 to NUM_ATTEMPTS$
                            If AttemptNo GT 1 then Delay(1)                        
                            Lock BangTable, 0 then
                                Read PendingTrans from BangTable, 0 else PendingTrans = '0':@FM
                                PendingTrans := IndexTransactionRow
                                Write PendingTrans on BangTable, 0 then
                                    LogData<1> = OConv(Datetime(), 'DT/^S')
                                    LogData<4> = 'Index transaction successfully added.'
                                    Logging_Services('AppendLog', objVerifyWMOWoIndexLog, LogData, @RM, @FM)
                                    Done       = True$
                                end else
                                    ErrorMsg = 'Error in ':Service:' service. Unable to write index transaction to !WM_OUT. ':WMOKey
                                end
                                Unlock BangTable, 0 else ErrorMsg = 'Error in ':Service:' service. Unable to Open !WM_OUT to add index transaction. ':WMOKey
                            end else
                                If AttemptNo GE NUM_ATTEMPTS$ then 
                                    ErrorMsg = 'Error in ':Service:' service. Unable to Lock !WM_OUT to add index transaction. ':WMOKey
                                end
                            end
                        Until Done or ErrorMsg
                        Next AttemptNo
                    end else
                        ErrorMsg = 'Error in ':Service:' service. Unable to Open !WM_OUT to add index transaction. ':WMOKey
                    end
                end
            end else
                LogData<1> = OConv(Datetime(), 'DT/^S')
                LogData<4> = 'WONo for WM_OUT ':WMOKey:' is null. Nothing to update.'
                Logging_Services('AppendLog', objVerifyWMOWoIndexLog, LogData, @RM, @FM)                    
            end
        end
    end else
        ErrorMsg = 'Error in ':Service:' service. Null WMOKey passed in.'
    end
    
    If ErrorMsg NE '' then
        LogData<1> = OConv(Datetime(), 'DT/^S')
        LogData<4> = ErrorMsg
        Logging_Services('AppendLog', objVerifyWMOWoIndexLog, LogData, @RM, @FM)
    end
    
    LogData<1> = OConv(Datetime(), 'DT/^S')
    LogData<4> = 'End ':Service
    Logging_Services('AppendLog', objVerifyWMOWoIndexLog, LogData, @RM, @FM)
    
    If ErrorMsg NE '' then Error_Services('Add', ErrorMsg)
    
end service


Service VerifyWOMatWMOKeyIndex(WMOKey)
    
    LogPath                 = Environment_Services('GetApplicationRootPath') : '\LogFiles\WM_OUT'
    LogDate                 = Oconv(Date(), 'D4/')
    LogTime                 = Oconv(Time(), 'MTS')
    LogFileName             = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' !WM_OUT WO_MAT{WMO_KEY} Log.csv'
    Headers                 = 'Logging DTM':@FM:'WMOKey':@FM:'WOMatKey':@FM:'Result'
    objVerifyWOMatWmoKeyLog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, COMMA$, Headers, '', False$, False$)
    
    LogData    = ''
    LogData<1> = OConv(Datetime(), 'DT/^S')
    LogData<2> = WMOKey
    LogData<4> = 'Begin ':Service
    Logging_Services('AppendLog', objVerifyWOMatWmoKeyLog, LogData, @RM, @FM)
    
    ErrorMsg = ''
    If WMOKey NE '' then
        If RowExists('WM_OUT', WMOKey) then
            WOMatKey = Field(WMOKey, '*', 1, 1):'*':Field(WMOKey, '*', 3, 1)
            If WOMatKey NE '' then
                WOMatWMOKey = Xlate('WO_MAT', WOMatKey, 'WMO_KEY', 'X')
                LogData<1>  = OConv(Datetime(), 'DT/^S')
                LogData<3>  = WOMatKey
                Logging_Services('AppendLog', objVerifyWOMatWmoKeyLog, LogData, @RM, @FM)               
                If WOMatWMOKey EQ '' then
                    LogData<1>          = OConv(Datetime(), 'DT/^S')
                    LogData<4>          = 'WMOKey missing from WO_MAT record. Generating index transaction.'
                    Logging_Services('AppendLog', objVerifyWOMatWmoKeyLog, LogData, @RM, @FM)     
                    // Add index transaction to update WMO_KEY relational index (target WO_MAT table)
                    IndexTransactionRow = 'WO_MAT*WMO_KEY*TOP':@FM:WMOKey:@FM:"":@FM:WOMatKey:@FM
                    Open "!WM_OUT" to BangTable then
                        Done = False$
                        For AttemptNo = 1 to NUM_ATTEMPTS$
                            If AttemptNo GT 1 then Delay(1)
                            Lock BangTable, 0 then
                                Read PendingTrans from BangTable, 0 else PendingTrans = '0':@FM
                                PendingTrans := IndexTransactionRow
                                Write PendingTrans on BangTable, 0 then
                                    LogData<1> = OConv(Datetime(), 'DT/^S')
                                    LogData<4> = 'Index transaction successfully added.'
                                    Logging_Services('AppendLog', objVerifyWOMatWmoKeyLog, LogData, @RM, @FM)
                                    Done       = True$
                                end else
                                    ErrorMsg = 'Unable to write index transaction to !WM_OUT. ':WMOKey
                                end
                                Unlock BangTable, 0 else ErrorMsg = 'Unable to Open !WM_OUT to add index transaction. ':WMOKey
                            end else
                                If AttemptNo GE NUM_ATTEMPTS$ then 
                                    ErrorMsg = 'Unable to Lock !WM_OUT to add index transaction. ':WMOKey
                                end
                            end
                        Until Done or ErrorMsg
                        Next AttemptNo
                    end else
                        ErrorMsg = 'Unable to Open !WM_OUT to add index transaction. ':WMOKey
                    end                       
                end
            end else
                LogData<1> = OConv(Datetime(), 'DT/^S')
                LogData<4> = 'WO_MAT key for WM_OUT ':WMOKey:' is null. Nothing to update.'
                Logging_Services('AppendLog', objVerifyWOMatWmoKeyLog, LogData, @RM, @FM)                    
            end
        end
    end
    
    If ErrorMsg NE '' then
        LogData<1> = OConv(Datetime(), 'DT/^S')
        LogData<4> = ErrorMsg
        Logging_Services('AppendLog', objVerifyWOMatWmoKeyLog, LogData, @RM, @FM)
    end
    
    LogData<1> = OConv(Datetime(), 'DT/^S')
    LogData<4> = 'End ':Service
    Logging_Services('AppendLog', objVerifyWOMatWmoKeyLog, LogData, @RM, @FM)
    
    If ErrorMsg NE '' then Error_Services('Add', ErrorMsg)
    
end service


Service GetWMOutKeys(WOLogId)
    
    ErrorMsg = ''
    WMOutKeys = ''
    
    If WOLogId NE '' then
        Open 'DICT.WM_OUT' to hWmOutDict then
            SearchString = ''
            SearchString := 'WO_NO':@VM:WOLogId:@FM
            Btree.Extract(SearchString, 'WM_OUT', hWmOutDict, WMOutKeys, 0, 0)
        end else
            ErrorMsg = 'Error opening WM_OUT dictionary.'
        end
    end else
        ErrorMsg = 'WOLogId was null'
    end
    
    If ErrorMsg NE '' then
        Error_Services('Add', ErrorMsg)
    end
    
    Response = WMOutKeys
    
end service


Service GetWmOutZpl(WmOutKey)
    
    ZPL        = ''
    ErrorMsg = ''
    If WmOutKey NE '' then
        Swap '-' with '*' in WmOutKey
        Swap '.' with '*' in WmOutKey
        If RowExists('WM_OUT', WmOutKey) then
            WONo       = Field(WmOutKey, '*', 1, 1)
            WOStep     = Field(WmOutKey, '*', 2, 1)
            WOStepKeys = XLATE('WO_LOG', WONo, WO_LOG_WO_STEP_KEY$, 'X')
            IF INDEX(WOStepKeys,@VM,1) THEN
                PrintWONo = WONo:'.':WOStep
            END ELSE
                PrintWONo = WONo
            END
            WORec       = XLATE('WO_LOG',WONo,'','X')
            ReqShipDate	= OCONV(WORec<WO_LOG_PROMISE_SHIP_DT$>,'D2/')
            CustNo		= WORec<WO_LOG_CUST_NO$>
            CustName	= XLATE('COMPANY',CustNo,'ABBREV_OR_CO_NAME','X')
            Captive		= XLATE('COMPANY',CustNo,'CAPTIVE','X')
            PONo		= WORec<WO_LOG_CUST_PO_NO$>
            PSNo        = XLATE('WO_STEP',WONo:'*':WOStep,WO_STEP_PROD_SPEC_ID$,'X')
            PSRec       = XLATE('PROD_SPEC',PSNo,'','X')
            
            CustSpecNo = ''
            IF Captive THEN
                CustSpecNos    = PSRec<PROD_SPEC_SPEC_NUM$>
                CustSpecDescs  = PSRec<PROD_SPEC_SPEC_DESC$>
                CustSpecRevNos = PSRec<PROD_SPEC_REV_NUM$>
                CustSpecRevDts = PSRec<PROD_SPEC_REV_DATE$>
                SpecCnt        = COUNT(CustSpecNos,@VM) + (CustSpecNos NE '')
                FOR I = 1 TO SpecCnt
                    IF CustSpecDescs<1,I> _EQC 'GENERAL' THEN
                        CustSpecNo		= CustSpecNos<1,I>
                        CustSpecDesc	= CustSpecDescS<1,I>
                        CustSpecRevNo	= CustSpecRevNos<1,I>
                        CustSpecRevDt	= OCONV(CustSpecRevDts<1,I>,'D4/')
                    END
                UNTIL CustSpecNo NE ''
                NEXT I
            END
            
            EpiPartNo               = WORec<WO_LOG_EPI_PART_NO$>
            CustEpiPartRec          = XLATE('CUST_EPI_PART',CustNo:'*':EpiPartNo,'','X')
            ShipBagReq              = CustEpiPartRec<CUST_EPI_PART_SHIP_BAG_REQ$>
            PreSurfscan				= XLATE('PRS_STAGE',PSNo:'*PRE',PRS_STAGE_SURFSCAN_SIG_REQ$,'X')
            FirstSurfscan			= XLATE('PRS_STAGE',PSNo:'*WFR',PRS_STAGE_SURFSCAN_SIG_REQ$,'X')
            PostCleanSurfScan		= XLATE('PRS_STAGE',PSNo:'*POST',PRS_STAGE_SURFSCAN_SIG_REQ$,'X')
            PostCleanSSSampleQty	= XLATE('PRS_STAGE',PSNo:'*POST',PRS_STAGE_SS_SAMP_QTY$, 'X' )
            PostCleanSurfscanRecipe	= XLATE('PRS_STAGE',PSNo:'*POST',PRS_STAGE_SURFSCAN_RECIPE$,'X')
            PostCleanSurfDefects	= XLATE('PRS_STAGE',PSNo:'*POST',PRS_STAGE_SURF_DEFECTS$,'X')
            PostCleanSurfHaze		= OCONV(XLATE('PRS_STAGE',PSNo:'*POST',PRS_STAGE_SURF_HAZE$,'X'),'MD2')
            SubPreClean             = XLATE('PRS_STAGE',PSNo:'*PRE','TOOL_DISPLAY','X')
            SubPostClean            = XLATE('PRS_STAGE',PSNo:'*POST','TOOL_DISPLAY','X')
            PreCleanTool            = XLATE('PRS_STAGE',PSNo:'*PRE','TOOL_DISPLAY','X')					
            PostCleanTool           = XLATE('PRS_STAGE',PSNo:'*POST','TOOL_DISPLAY','X')
            PreAkrionRecipe         = XLATE('PRS_STAGE',PSNo:'*PRE',PRS_STAGE_CLEAN_RECIPE$,'X') 
            PostAkrionRecipe        = XLATE('PRS_STAGE',PSNo:'*POST',PRS_STAGE_CLEAN_RECIPE$,'X')
            * * * * * * * *  Following needs update to point to the PRS_LAYER & PRS_STAGE tables as required.
            * * * * * * * *  It's the required part that needs sorted out JCH 1/28/2014
            RecipeNo			= XLATE( 'PROD_SPEC', PSNo, 'RECIPE_NO_L1', 'X' )
            RecipeName			= XLATE( 'PROD_SPEC', PSNo, 'RECIPE_NAME_L1', 'X' )
            SpecType			= OCONV( XLATE( 'PROD_SPEC', PSNo, 'SPEC_TYPE', 'X' ), '[SPEC_TYPE_CONV]' )
            SubOxide			= OCONV( XLATE( 'PROD_SPEC', PSNo, 'SUB_OXIDE_STRIP', 'X' ), 'B' )
            ThickTarget			= OCONV( XLATE( 'PROD_SPEC', PSNo, 'THICK_TARGET_ALL', 'X' ), 'MD3' )
            ResTarget			= OCONV( XLATE( 'PROD_SPEC', PSNo, 'RES_TARGET_ALL', 'X' ), 'MD3' )
            ThickUnit			= XLATE( 'PROD_SPEC', PSNo, 'THICK_UNITS_ALL', 'X' )
            ResUnit				= XLATE( 'PROD_SPEC', PSNo , 'RES_UNITS_ALL', 'X' )
            Dopant				= XLATE( 'PROD_SPEC', PSNo, 'DOPANT_L1', 'X' )
            RecipeNo			= XLATE( 'PROD_SPEC', PSNo, 'RECIPE_NO', 'X' )	
            RecipeInfo			= XLATE( 'RECIPE', RecipeNo, 'RECIPE_NAME_NO', 'X' )
            CleaningReqs		= ''
            
            ThickCnt = FIELDCOUNT( ThickTarget<1>, @VM )
            PrintThickTargets = ''
            FOR J  = 1 TO ThickCnt
                PrintThickTargets<1,J> = ThickTarget<1,J>:ThickUnit<1,J>
            NEXT J 
            
            ResCnt = FIELDCOUNT( ResTarget<1>, @VM )
            PrintResTargets = ''
            FOR J  = 1 TO ResCnt
                IF ResTarget<1,J>[1,1] = '.' THEN
                    TargetVal = '0':ResTarget<1,J>
                END ELSE
                    TargetVal = ResTarget<1,J>
                END
                PrintResTargets<1,J> = TargetVal:ResUnit<1,J>
            NEXT J 
            
            APreRec  = ''
            APostRec = ''
            IF ( PreAkrionRecipe<1> <> '' ) THEN
                APreRec = ' ':PreAkrionRecipe:' '
                SubOxide = 'No' 					;* If Akrion then no oxide strip
            END
            
            IF ( PostAkrionRecipe<1> <> '' ) THEN
                APostRec = ' ':PostAkrionRecipe
            END
            
            PrintCleaningReqs	= TRIM( 'Strip:':SubOxide:' Pre:':SubPreClean:APreRec:' Post:':SubPostClean:APostRec )
            
            swap UNIT_MICROMETER$ with 'um' in PrintThickTargets
            swap UNIT_OHM_CM$     with 'ohm.cm' in PrintThickTargets
            swap UNIT_OHM_PER_SQ$ with 'ohm/sq' in PrintThickTargets
            swap UNIT_A$ with 'A' in PrintThickTargets  
            
            swap UNIT_MICROMETER$ with 'um' in PrintResTargets
            swap UNIT_OHM_CM$     with 'ohm.cm' in PrintResTargets
            swap UNIT_OHM_PER_SQ$ with 'ohm/sq' in PrintResTargets
            swap UNIT_A$ with 'A' in PrintResTargets
            
            MakeupBox = XLATE('WM_OUT',WMOutKey,WM_OUT_MAKEUP_BOX$ ,'X')
            
            PrintWMOutKey = WMOutKey
            CONVERT '*' TO '.' IN PrintWMOutKey
            
            CassNo       = FIELD(WMOutKey,'*',3)
            LotNo        = 'Multiple'
            WfrQty       = XLATE('WM_OUT',WMOutKey,'WFRS_SCHED','X')
            SubPartNo    = WORec<WO_LOG_ORD_SUB_PART_NO$>
            CustPartNo   = WORec<WO_LOG_CUST_PART_NO$>
            EpiPartNo    = WORec<WO_LOG_EPI_PART_NO$>
            CustPartDesc = XLATE('EPI_PART',EpiPartNo,EPI_PART_EPI_PART_DESC$,'X')
            PartNo       = CustPartNo				;* Temporary for 4x2 to 4x4 conversion
            PartDesc     = CustPartDesc			;* Temporary for 4x2 to 4x4 conversion
            SubVendCode  = WORec<WO_LOG_EXP_VEND_CD$>
            Vendor       = XLATE('SEMI_VEND_CODE', SubVendCode, 'EPI_VEND_CD', 'X')
            
            SuppCd = TRIM(SubPartNo[-1,'B-'])
            IF LEN(SuppCd) NE '2' THEN SuppCd =''
            
            For cnt = 1 to 2
                ***** Start of label ZPL
                ZPL := '^XA'
                ***** Label setup
                ZPL := '^LH0,0'
                ZPL := '^PR2'		;* Print speed 2 inches per second
                ZPL := '^LL406'		;* Label length 203 dpi
                ZPL := '^PW900'		;* Print width 900 dpi
                ZPL := '^MD2'		;* Media darkness
                ZPL := '^MMT':CRLF$	;* Media mode t=tear off mode
                ***** Border
                ZPL := '^FO10,5^GB795,398,3,B^FS':CRLF$				
                ****** First Line
                Company = 'IFX Epi Services'
                ZPL := '^FO30,20^A0,45,36^FD':Company:'^FS':CRLF$	
                ZPL := '^FO290,25^AC,18^FDWO No:^FS':CRLF$					
                ZPL := '^FO370,20^A045,36^FD':PrintWONo:'^FS':CRLF$			
                IF CassNo = 1 THEN
                    ZPL := '^FO535,15^GB120,40,40,,3^FS':CRLF$
                    ZPL := '^FO540,25^AC,18^FR^FDCass:^FS':CRLF$			
                    ZPL := '^FO600,20^A045,36^FR^FD':CassNo:'^FS':CRLF$		
                END ELSE
                    ZPL := '^FO540,25^AC,18^FDCass:^FS':CRLF$				
                    ZPL := '^FO600,20^A045,36^FD':CassNo:'^FS':CRLF$		
                END
                ZPL:= '^FO665,20^A045,36^FDWM Out^FS':CRLF$
                ****** Second Line
                ZPL:= '^FO30,70^AC,18^FDWM Out:^FS':CRLF$
                ZPL:= '^FO120,65^A050,30^FD':PrintWMOutKey:'^FS':CRLF$
                ZPL := '^BY2,2.0':CRLF$
                ZPL := '^FO300,58^B3,,37,N^FD':'O':PrintWMOutKey:'^FS':CRLF$
                ZPL := '^BY2,3.0':CRLF$
                ZPL:= '^FO700,60^AC,18^FDQty:^FS':CRLF$						
                ZPL:= '^FO760,60^A045,25^FD':WfrQty:'^FS':CRLF$				
                ****** Third Line
                ZPL:= '^FO30,100^AC,18^FDShip Dt:^FS':CRLF$		
                ZPL:= '^FO140,100^A0,,25^FD':ReqShipDate:'^FS':CRLF$		
                ZPL:= '^FO250,100^AC,18^FDCust:^FS':CRLF$					
                ZPL:= '^FO320,100^A045,25^FD':CustName:'^FS':CRLF$			
                ZPL:= '^FO560,100^AC,18^FDPO:^FS':CRLF$
                ZPL:= '^FO600,100^A045,25^FD':PONo:'^FS':CRLF$
                ****** Separator bar
                ZPL:= '^FO15,125^GB790,1^FS':CRLF$
                ****** Fourth Line
                ZPL := '^FO30,130^AC,18^FDLot No:^FS':CRLF$
                ZPL := '^FO120,130^A0,20^FD':LotNo:'^FS':CRLF$
                ZPL := '^FO410,130^AC,18^FDPart No:^FS':CRLF$
                ZPL := '^FO510,130^A0,20^FD':PartNo:'^FS':CRLF$
                ****** Fifth Line
                ZPL := '^FO30,155^AC,18^FDSub PN:^FS':CRLF$
                ZPL := '^FO120,155^A0,20^FD':SubPartNo:'^FS':CRLF$
                IF SuppCd NE '' THEN
                    ZPL := '^FO410,155^AC,18^FDSupp Cd:^FS':CRLF$
                    ZPL := '^FO510,155^A0,20^FD':SuppCd:'^FS':CRLF$			
                    ZPL := '^BY2,2.0':CRLF$
                    ZPL := '^FO610,155^A040,20^B3,,23,N^FD':SuppCd:'^FS':CRLF$
                    ZPL := '^BY2,3.0':CRLF$
                END
                ****** Separator bar
                ZPL := '^FO15,180^GB790,1^FS':CRLF$
                IF MakeupBox THEN
                    ZPL := '^FO410,190^A050,30^FD* * *  M a k e u p   B o x  * * *^FS':CRLF$
                END ELSE
                    ZPL := '^FO410,190^AC,18^FDReact Type:^FS':CRLF$
                    ZPL := '^FO560,190^A0,20^FDEpiPro^FS':CRLF$
                END
                ****** Seventh Line
                ZPL := '^FO30,190^AC,18^FDPre:^FS':CRLF$
                ZPL := '^FO90,190^A0,20^FD':SubPreClean:'^FS':CRLF$
                IF PreAkrionRecipe NE '' THEN
                    ZPL := '^FO210,190^AC,18^FDAkrion:^FS':CRLF$
                    ZPL := '^FO300,190^A0,20^FD':PreAkrionRecipe:'^FS':CRLF$
                    ZPL := '^BY2,2.0':CRLF$
                    ZPL := '^FO450,190^A040,20^B3,,23,N^FD':PreAkrionRecipe:'^FS':CRLF$
                    ZPL := '^BY2,3.0':CRLF$
                END
                ****** Eighth Line
                ZPL := '^FO30,225^AC,18^FDRecipe:^FS':CRLF$
                ZPL := '^FO120,225^A0,20^FD':RecipeInfo:'^FS':CRLF$
                ZPL := '^FO410,225^AC,18^FDEpi Dopant:^FS':CRLF$
                ZPL := '^FO560,225^A040,20^FD':Dopant:'^FS':CRLF$
                ****** Ninth, Tenth and Eleventh Lines
                ZPL := '^FO30,260^AC,18^FDThk Spec:^FS':CRLF$
                FOR M = 1 TO COUNT(PrintThickTargets,@VM) + (PrintThickTargets NE '')
                    BaseLine = 260 + (M-1)*20
                    ZPL := '^FO140,':BaseLine:'^A040,20^FD':PrintThickTargets<1,M>:'^FS':CRLF$
                NEXT M
                ZPL := '^FO410,260^AC,18^FDRes Spec:^FS':CRLF$
                FOR M = 1 TO COUNT(PrintResTargets,@VM) + (PrintResTargets NE '')
                    BaseLine = 260 + (M-1)*20
                    ZPL := '^FO520,':BaseLine:'^A040,20^FD':PrintResTargets<1,M>:'^FS':CRLF$
                NEXT M
                IF PostAkrionRecipe NE '' THEN
                    ZPL := '^FO30,335^AC,18^FDPost:^FS':CRLF$
                    ZPL := '^FO90,335^A0,20^FD':SubPostClean:'^FS':CRLF$
                    ZPL := '^FO210,335^AC,18^FDAkrion:^FS':CRLF$
                    ZPL := '^FO300,335^A0,20^FD':PostAkrionRecipe:'^FS':CRLF$
                    ZPL := '^BY2,2.0':CRLF$
                    ZPL := '^FO450,335^A040,20^B3,,23,N^FD':PostAkrionRecipe:'^FS':CRLF$
                    ZPL := '^BY2,3.0':CRLF$
                END ELSE
                    ZPL := '^FO30,335^AC,18^FDPost:^FS':CRLF$
                    ZPL := '^FO120,335^A0,20^FD':SubPostClean:'^FS':CRLF$
                END
                ****** Separator bar
                ZPL := '^FO15,365^GB790,1^FS':CRLF$
                ****** Data Matrix barcode
                ZPL	:= '^FO720,280^CI28':CRLF$
                ZPL	:= '^BXN,2,200^FDP':PartNo:'|S':SubPartNo:'|1T':PrintWMOutKey:'|2T':LotNo:'|':PSNo:'|Q':WfrQty:'|1V':Vendor:'|SEQ':Cnt:'^FS':CRLF$
                ****** Fourteenth Line
                ZPL := '^FO30,375^AC,18^FDProd Spec No:^FS':CRLF$			
                ZPL := '^FO190,375^A0,25^FD':PSNo:'^FS':CRLF$				
                IF SpecType = 'Production' THEN SpecType = 'Prod'
                IF SpecType = 'Pre-Production' THEN SpecType = 'Pre'
                ZPL := '^FO275,375^AC,18^FDSpec Type:^FS':CRLF$				
                ZPL := '^FO400,375^A0,25^FD':SpecType:'^FS':CRLF$			
                ZPL := '^FO480,375^AC,18^FDBag:^FS':CRLF$
                ZPL := '^FO530,375^A0,20^FD':ShipBagReq:'^FS':CRLF$
                ***** End of Label
                ZPL := '^XZ'
            Next
        end else
            ErrorMsg = 'Error in ':Service:' service. WM_OUT ':WmOutKey:' does not exist.'
        end
    end else
        ErrorMsg = 'Error in ':Service:' service. Null WmOutKey passed in.'
    end
    
    If ErrorMsg EQ '' then
        Response = ZPL
    end else
        Error_Services('Add', ErrorMsg)
    end
    
end service



