giovedì 25 aprile 2019

Lettura Ordine di Produzione da Excel (con problemi!).

La BAPI_PRODORD_GET_DETAIL è un Function Module usato per leggere dati dagli Ordini di Produzione.

Ho pensato di usare questa funzione da un foglio Excel per appunto, da un ordine di produzione, trovare i suoi dati di testata, le operazioni di lavoro e i componenti usati.


Queste le versioni su cui ho lavorato:

  • Windows 10 pro
  • Excel 2016
  • SAPGui 750 comp. 2
  • SAP R/3 EHP8

Ho incontrato le solite difficoltà con il passaggio dei parametri e la lettura dei dati di ritorno, inoltre ho trovato un paio di problemi che non ho risolto e che penso possano essere degli errori del FM chiamato da remoto.

Intanto faccio notare che un FM si può chiamare da remoto se è impostato questo parametro:


Per la BAPI in oggetto questi sono i parametri di importazione:


Si deve passare il numero ordine, poi c'è un parametro facoltativo, quindi una struttura dove selezionare i dati che si vogliono leggere:


In esportazione troviamo la classica struttura RETURN dei messaggi delle BAPI:


Quindi le tabelle di ritorno con i dati letti:


Questo è come ho pensato di impostare il file Excel.

All'avvio vengono puliti eventuali dati precedenti e si presenta così:


L'utente deve inserire il numero ordine da leggere e premere il pulsante "Leggi Ordine":


La macro collegata al pulsante esegue l'accesso al sistema R/3, chiama la BAPI_PRODORD_GET_DETAIL e, con i dati di ritorno, va a compilare il foglio con i dati di testata, le operazione e sotto i componenti.

Questo dopo la lettura:


(ho anonimizzato materiali, centri di lavoro e operazioni che vengono visualizzati con caratteri in corsivo)

Da notare che i componenti vengono scritti sotto le operazioni in modo dinamico, lasciando una riga vuota, creando la riga di intestazione con copia di formattazione dalla intestazione delle operazioni.

Questi i punti a cui ho dovuto prestare attenzione:

Il numero ordine deve essere passato con gli zeri iniziali (12 caratteri)

Ho utilizzato questo comando (trovato sul web):

String$(12 - Len(Sheets("ODP").Cells(2, 2).Value), "0") & Sheets("ODP").Cells(2, 2).Value

Le quantità, se copiate direttamente nelle celle, erano riportate moltiplicate per 1.000.

All'inizio avevo risolto semplicemente moltiplicando per 1 e veniva corretto, poi ho usato una funzione di conversione standard:

h_qta = CDbl(HEADER(1, 18))

che converte una stringa in un Double. Attenzione a non usare la CLng che converte in un Long ma senza decimali.

Unità di Misura.

La funzione ritorna 2 unità di misura:

  • Interna SAP: è una codifica standard usata internamente da SAP, per esempio "ST" da pezzo in tedesco "Stück".
  • ISO: codifica internazionale, per esempio per pezzo "PCE".


mi sono appoggiato a quella interna SAP ed ho costruito una funzione dove effettuare le conversioni:

'------------------------------------------------
' Conversione UM
'------------------------------------------------
Function conv_um(ByVal um_inp As String) As String

  If um_inp = "ST" Then
    um_inp = "PZ"
  End If
  
  conv_um = um_inp

End Function

Eventualmente da estendere con la conversione di altre sigle.

Adesso i problemi incontrati:

1) La struttura RETURN risulta sempre vuota.

Ho fatto diversi tentativi, questo il comando che dovrebbe popolare la struttura:

Set PRETURN = myFuncBPGD.imports("RETURN")

Come indicato in vari forum deve essere usato prima della chiamata alla funzione, ma ho provato sia prima, che dopo, che entrambe, ma la struttura risulta sempre vuota.

Chiaramente, se si chiama la funzione da dentro SAP, la struttura restituisce correttamente i messaggi di ritorno, per esempio se un ordine non esiste:


A questo problema ho trovato soluzione controllando se la struttura HEADER di ritorno aveva almeno un record, allora la chiamata era corretta altrimenti invio un generico messaggio di errore:

num_Row_head = HEADER.RowCount

If num_Row_head = 0 Then

  MsgBox "Errore lettua ODP!!!"
  GoTo FINE
End If

2) Quantità superiori a 99.999 non vengono rilevate.

Questo purtroppo è un errore grave a cui non ho trovato soluzione.

Tutte le quantità, sia nella testata che nelle operazioni o componenti, presentano questo problema.

Ho provato a creare un ordine con quantità grandi sia in testata che nei componenti, da dentro R/3 i dati ritornano correttamente, per esempio sui componenti:


mentre da chiamata Excel ritorna:


Non mi sembra un problema di conversione poichè, anche in debug sulla struttura di ritorno dei dati, il campo 12 che contiene la quantità è a 0:


Qui potete scaricare il foglio excel di esempio: Lettura_ODP.xls

Alcune annotazioni generali:

  • Di norma includo le informazioni di Logon nel codice VBA e poi lo proteggo con una password poichè altrimenti sono in chiaro. Anche così non è che sia una protezione molto solida, ma mi sembra il minimo.
  • nel PC deve essere installata la SAPGUI (che contiene le DLL per poter attivare il collegamento).
  • se nel server SAP è attivo il firewall deve essere aperta in ingresso la porta tcp 33nn (nn=<system number>), normalmente per le connessioni sapgui è aperta la 32nn.
  • consiglio di definire un utente di tipo S (servizio) che, in pratica, non può effettuare il logon da GUI.

Riporto le macro contenute nel foglio.

Macro collegata al pulsante:

Public ok_login As Integer

Sub main()

Dim R3 As Object

' Pulisco dati

Range("C2:I2").Select
Selection.ClearContents
Rows("6:200").Select
Selection.Delete Shift:=xlUp
Range("A6").Select

Set R3 = CreateObject("SAP.Functions")


zlogin R3


If ok_login = 0 Then

  LeggiODP R3
  R3.Connection.Logoff
End If

End Sub


Macro Login a R/3:

Sub zlogin(R3 As Object)

R3.Connection.ApplicationServer = Sheets("Logon").Cells(1, 2).Value

R3.Connection.SystemNumber = Sheets("Logon").Cells(2, 2).Value
R3.Connection.User = Sheets("Logon").Cells(3, 2).Value
R3.Connection.Password = Sheets("Logon").Cells(4, 2).Value
R3.Connection.client = Sheets("Logon").Cells(5, 2).Value
R3.Connection.Language = Sheets("Logon").Cells(6, 2).Value

'R3.Connection.ApplicationServer = ""

'R3.Connection.SystemNumber = nn
'R3.Connection.User = ""
'R3.Connection.Password = ""
'R3.Connection.client = nnn
'R3.Connection.Language = ""

' Logon a SAP e controllo se la connessione è stabilita

If R3.Connection.Logon(0, True) <> True Then
  MsgBox "Impossibile collegarsi a SAP!" 'Messaggio di errore
  ok_login = 1
  Exit Sub
Else
  'MsgBox "OK: Collegato a SAP!"
  ok_login = 0
End If

End Sub


Nota: se volete riportare direttamente nel codice i parametri di accesso commentate le righe sopra, che vanno a prendere i dati dal foglio Logon, e riportateli nel codice commentato sotto.

Macro lettura ODP:

Sub LeggiODP(R3 As Object)

' Controllo inserimenti

odp = Sheets("ODP").Cells(2, 2).Value

If odp = "" Then

   MsgBox "Scegliere ODP!!!"
   Exit Sub
End If

' Definizione funzione

Set myFuncBPGD = R3.Add("BAPI_PRODORD_GET_DETAIL")

Dim NUMBER, ORDER_OBJECTS As Object

Dim PRETURN As Object
Dim HEADER, POSITION, SEQUENCE, OPERATION, TRIGGER_POINT, COMPONENT, PROD_REL_TOOL, FSH_BUNDLES, FSH_PRODORD_SEASON As Object

' Imposto parametri in ingresso della Funzione


' Parametri in ingresso

Set NUMBER = myFuncBPGD.exports("NUMBER")
Set ORDER_OBJECTS = myFuncBPGD.exports("ORDER_OBJECTS")

' Esito chiamata alla BAPI

Set PRETURN = myFuncBPGD.imports("RETURN")

' Valorizzo i parametri in ingresso con i campi del foglio

NUMBER.Value = String$(12 - Len(Sheets("ODP").Cells(2, 2).Value), "0") & Sheets("ODP").Cells(2, 2).Value
ORDER_OBJECTS.Value(1) = "X"  ' Header
ORDER_OBJECTS.Value(4) = "X"  ' Operations
ORDER_OBJECTS.Value(5) = "X"  ' Components

' Chiamata alla funzione SAP

If myFuncBPGD.call = False Then
   MsgBox myFunc.Exception
   GoTo FINE
End If
    
' Ricezione dati di ritorno dalla Funzione
Set HEADER = myFuncBPGD.Tables("HEADER")
Set OPERATION = myFuncBPGD.Tables("OPERATION")
Set COMPONENT = myFuncBPGD.Tables("COMPONENT")

' Leggo eventuali messaggi di ritorno e li mostro all'utente

Dim msx As String
    
'----------- NOTA: non funziona! PRETURN ritorna sempre vuoto.
'num_Row = PRETURN.RowCount
'
'For iRow = 1 To num_Row
'   msx = PRETURN.Value(iRow, 4)
'   MsgBox msx
'   riga = "Messaggi =" & msx:   Print #1, riga
'Next
'----------- FINE
    
num_Row_head = HEADER.RowCount

If num_Row_head = 0 Then

  MsgBox "Errore lettua ODP!!!"
  GoTo FINE
End If

'------------------------------------------------

' Ricavo i dati di testata
'------------------------------------------------
h_werks = HEADER(1, 2)
h_matnr = HEADER(1, 5)
h_data = HEADER(1, 11)
h_qta = CDbl(HEADER(1, 18))
h_um = HEADER(1, 19)
h_tipo = HEADER(1, 22)
h_descr = HEADER(1, 41)

' Conversione UM

h_um = conv_um(h_um)

' Metto i dati di testata nel foglio

Sheets("ODP").Cells(2, 3) = h_werks
Sheets("ODP").Cells(2, 4) = h_matnr
Sheets("ODP").Cells(2, 5) = h_descr
Sheets("ODP").Cells(2, 6) = h_qta
Sheets("ODP").Cells(2, 7) = h_um
Sheets("ODP").Cells(2, 8) = h_data
Sheets("ODP").Cells(2, 9) = h_tipo

'------------------------------------------------

' compilo operazioni
'------------------------------------------------
num_Row_ope = OPERATION.RowCount

For iRow = 1 To num_Row_ope


  ' Ricavo i dati delle operazioni

  o_fase = OPERATION(iRow, 11)
  o_chiave = OPERATION(iRow, 12)
  o_werks = OPERATION(iRow, 13)
  o_testo = OPERATION(iRow, 14)
  o_um = OPERATION(iRow, 23)
  o_qta = CDbl(OPERATION(iRow, 25))
  o_data = OPERATION(iRow, 31)
  o_cdl = OPERATION(iRow, 43)
  o_cdl_desc = OPERATION(iRow, 44)

  ' Conversione UM

  o_um = conv_um(o_um)
  
  ' Metto i dati delle operazioni nel foglio
  Sheets("ODP").Cells(5 + iRow, 1) = o_fase
  Sheets("ODP").Cells(5 + iRow, 2) = o_chiave
  Sheets("ODP").Cells(5 + iRow, 3) = o_cdl
  Sheets("ODP").Cells(5 + iRow, 4) = o_cdl_desc
  Sheets("ODP").Cells(5 + iRow, 5) = o_testo
  Sheets("ODP").Cells(5 + iRow, 6) = o_qta
  Sheets("ODP").Cells(5 + iRow, 7) = o_um
  Sheets("ODP").Cells(5 + iRow, 8) = o_data
  
Next

'------------------------------------------------

' Inserisco intestazione per componenti
'------------------------------------------------
n_riga_h_c = num_Row_ope + 7

Sheets("ODP").Cells(n_riga_h_c, 1) = "COMPONENTI"


n_riga_h_com = n_riga_h_c + 1


Sheets("ODP").Cells(n_riga_h_com, 1) = "Pos"

Sheets("ODP").Cells(n_riga_h_com, 2) = "Magazzino"
Sheets("ODP").Cells(n_riga_h_com, 3) = "Fase"
Sheets("ODP").Cells(n_riga_h_com, 4) = "Materiale"
Sheets("ODP").Cells(n_riga_h_com, 5) = "Descrizione"
Sheets("ODP").Cells(n_riga_h_com, 6) = "Q.tà"
Sheets("ODP").Cells(n_riga_h_com, 7) = "Um"
Sheets("ODP").Cells(n_riga_h_com, 8) = "Data"

' copio formato intestazione

Rows("4:5").Select
Selection.Copy
Rows(n_riga_h_c & ":" & n_riga_h_com).Select
Selection.PasteSpecial Paste:=xlPasteFormats, OPERATION:=xlNone, SkipBlanks:=False, Transpose:=False
Application.CutCopyMode = False

'------------------------------------------------

' compilo componenti
'------------------------------------------------
num_Row_c = COMPONENT.RowCount

For iRow = 1 To num_Row_c


  ' Ricavo i dati dei componenti

  c_matnr = COMPONENT(iRow, 5)
  c_lgort = COMPONENT(iRow, 7)
  c_data = COMPONENT(iRow, 11)
  c_qta = CDbl(COMPONENT(iRow, 12))
  c_um = COMPONENT(iRow, 13)
  c_pos = COMPONENT(iRow, 22)
  c_fase = COMPONENT(iRow, 24)
  c_desc = COMPONENT(iRow, 28)
  
  c_um = conv_um(c_um)
  
  ' Metto i dati dei componenti nel foglio
  Sheets("ODP").Cells(n_riga_h_com + iRow, 1) = c_pos
  Sheets("ODP").Cells(n_riga_h_com + iRow, 2) = c_lgort
  Sheets("ODP").Cells(n_riga_h_com + iRow, 3) = c_fase
  Sheets("ODP").Cells(n_riga_h_com + iRow, 4) = c_matnr
  Sheets("ODP").Cells(n_riga_h_com + iRow, 5) = c_desc
  Sheets("ODP").Cells(n_riga_h_com + iRow, 6) = c_qta
  Sheets("ODP").Cells(n_riga_h_com + iRow, 7) = c_um
  Sheets("ODP").Cells(n_riga_h_com + iRow, 8) = c_data
  
Next

FINE:


Set myFuncBGC = Nothing

      
Range("b2:b2").Select

End Sub

'------------------------------------------------
' Conversione UM
'------------------------------------------------
Function conv_um(ByVal um_inp As String) As String

  If um_inp = "ST" Then

    um_inp = "PZ"
  End If
  
  conv_um = um_inp

End Function


Nessun commento:

Posta un commento