Files
aufmass-web/_legacy/_tools/REB23_Aufmass_Tool (01).au3

681 lines
24 KiB
AutoIt
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include <Array.au3>
#include <File.au3>
#include <Date.au3>
#include <Debug.au3>
#include <GUIConstantsEx.au3>
; =========================================================
; INDUSTRIAL AUTOIT REB 23.003 → GAEB X31 ENGINE
; kompatibel mit DATAflor / California / GAEB DA XML
; =========================================================
Global $INPUT_FILE = @ScriptDir & "\..\Liebenau_BA_5_Mengennachweiß_AZ002_.txt"
Global $OUTPUT_FILE = @ScriptDir & "\..\export2.x31"
Global $g_iIDCnt = 1000000
; ── Einstiegspunkt ──────────────────────────────────────────
Local $importPfadTxt = @ScriptDir & "\..\Meckenbeueren_AZ10_.txt"
Local $ausgabePfadX31 = @ScriptDir & "\..\Meckenbeueren_AZ10_x31neu.x31"
Local $templateX31 = @ScriptDir & "\..\exporthallo.x31"
_X31_Export($importPfadTxt, $ausgabePfadX31)
Func _X31_Export($sImportTxt, $sAusgabeX31)
; ── 1. Datei einlesen ────────────────────────────────────
Local $hLese = FileOpen($sImportTxt, 0)
If $hLese = -1 Then
MsgBox(16, "Fehler", "Importdatei nicht gefunden:" & @CRLF & $sImportTxt)
Return SetError(1, 0, False)
EndIf
Local $aRawLines[10001]
$aRawLines[0] = 0
While True
Local $sZ = FileReadLine($hLese)
If @error = -1 Then ExitLoop
$sZ = StringReplace($sZ, Chr(13), "")
If $aRawLines[0] < 10000 Then
$aRawLines[0] += 1
$aRawLines[$aRawLines[0]] = $sZ
EndIf
WEnd
FileClose($hLese)
If $aRawLines[0] = 0 Then
MsgBox(16, "Fehler", "Importdatei ist leer.")
Return SetError(2, 0, False)
EndIf
; ── 2. Kopfdaten lesen ───────────────────────────────────
Local $aKopf[30][2]
Local $iKopfN = 0
Local $bInKopf = False
For $i = 1 To $aRawLines[0]
Local $sL = StringStripWS($aRawLines[$i], 3)
If StringInStr($sL, "[Kopfdaten]") Then
$bInKopf = True
ContinueLoop
EndIf
If StringLeft($sL, 1) = "[" And $bInKopf Then
$bInKopf = False
ContinueLoop
EndIf
If $bInKopf Then
Local $iG = StringInStr($sL, "=")
If $iG > 0 And $iKopfN < 28 Then
$aKopf[$iKopfN][0] = StringStripWS(StringLeft($sL, $iG - 1), 3)
$aKopf[$iKopfN][1] = StringStripWS(StringMid($sL, $iG + 1), 3)
$iKopfN += 1
EndIf
EndIf
Next
; Fallback: erste 15 Zeilen
If $iKopfN = 0 Then
For $i = 1 To ($aRawLines[0] < 15 ? $aRawLines[0] : 15)
Local $sF = StringStripWS($aRawLines[$i], 3)
Local $pF = StringInStr($sF, "=")
If $pF > 0 And $iKopfN < 28 Then
$aKopf[$iKopfN][0] = StringStripWS(StringLeft($sF, $pF - 1), 3)
$aKopf[$iKopfN][1] = StringStripWS(StringMid($sF, $pF + 1), 3)
$iKopfN += 1
EndIf
Next
EndIf
Local $sDatum = _KopfGet($aKopf, "Datum")
Local $sBaustelle = _KopfGet($aKopf, "Baustelle")
Local $sBauabs = _KopfGet($aKopf, "Bauabschnitt")
Local $sVertrag = _KopfGet($aKopf, "Vertrag")
Local $sAspaN = _KopfGet($aKopf, "AspaN")
Local $sAspaTel = _KopfGet($aKopf, "AspaTel")
Local $sAbrufNr = _KopfGet($aKopf, "AbrufNr")
Local $sSMNr = _KopfGet($aKopf, "SMNr")
Local $sStartZ = _KopfGet($aKopf, "StartZ")
Local $sEndZ = _KopfGet($aKopf, "EndZ")
Local $sISODatum = _DatumISO($sDatum)
Local $sStartISO = _DatumISO($sStartZ)
Local $sEndISO = _DatumISO($sEndZ)
Local $sUID = _UUID()
Local $sBoQUID = _UUID()
Local $sLVName = ($sVertrag <> "") ? $sVertrag : ($sBaustelle & " " & $sBauabs)
; ── 3. Aufmassdaten parsen ───────────────────────────────
; Spalten: 1=Ort|2=OZ|3=Faktor|4=Einzelmenge|5|6|7=GesamtMenge|
; 8=Einheit|9=Beschreibung|10=Bemerkung|11=Menge2|12=EP|13=GP
Local $aPosData[10001][9]
; [n][0]=OZ [n][1]=Qty(float) [n][2]=Ort [n][3]=Beschreibung
; [n][4]=Bemerkung [n][5]=Einheit [n][6]=EP [n][7]=Faktor [n][8]=EinzelMenge
Local $iPosN = 0
Local $bInData = False
For $i = 1 To $aRawLines[0]
Local $sLine = StringStripWS($aRawLines[$i], 3)
If StringInStr($sLine, "[Aufma") Then
$bInData = True
ContinueLoop
EndIf
If StringLeft($sLine, 1) = "[" And $bInData Then
$bInData = False
ContinueLoop
EndIf
If Not $bInData Then ContinueLoop
If $sLine = "" Then ContinueLoop
If StringStripWS(StringReplace($sLine, "|", ""), 3) = "" Then ContinueLoop
If Not StringInStr($sLine, "|") Then ContinueLoop
Local $aS = StringSplit($sLine, "|", $STR_NOCOUNT)
Local $nS = UBound($aS)
If $nS < 2 Then ContinueLoop
Local $sOZ = ($nS > 1) ? StringStripWS($aS[1], 3) : ""
If Not _IsValidOZ($sOZ) Then ContinueLoop
Local $sOrt = ($nS > 0) ? StringStripWS($aS[0], 3) : ""
Local $sFaktor = ($nS > 2) ? StringStripWS($aS[2], 3) : "1"
Local $sEinzel = ($nS > 3) ? StringStripWS($aS[3], 3) : ""
Local $sGesamt = ($nS > 6) ? StringStripWS($aS[6], 3) : ""
Local $sEinh = ($nS > 7) ? StringStripWS($aS[7], 3) : ""
Local $sBeschr = ($nS > 8) ? StringStripWS($aS[8], 3) : ""
Local $sBemerk = ($nS > 9) ? StringStripWS($aS[9], 3) : ""
Local $sEP = ($nS > 11) ? StringStripWS($aS[11], 3) : ""
; Qty berechnen: Faktor * Einzelmenge, oder direkt Gesamtmenge
Local $fFaktor = _ToFloat($sFaktor)
Local $fEinzel = _ToFloat($sEinzel)
Local $fGesamt = _ToFloat($sGesamt)
Local $fQty = 0
If $fFaktor <> 1 And $fEinzel <> 0 Then
$fQty = $fFaktor * $fEinzel
ElseIf $fGesamt <> 0 Then
$fQty = $fGesamt
ElseIf $fEinzel <> 0 Then
$fQty = $fEinzel
Else
$fQty = $fFaktor
EndIf
$aPosData[$iPosN][0] = $sOZ
$aPosData[$iPosN][1] = $fQty
$aPosData[$iPosN][2] = $sOrt
$aPosData[$iPosN][3] = $sBeschr
$aPosData[$iPosN][4] = $sBemerk
$aPosData[$iPosN][5] = $sEinh
$aPosData[$iPosN][6] = $sEP
$aPosData[$iPosN][7] = $sFaktor
$aPosData[$iPosN][8] = $sEinzel
$iPosN += 1
If $iPosN >= 10000 Then ExitLoop
Next
If $iPosN = 0 Then
MsgBox(16, "Fehler", "Keine gueltigen Positionen gefunden.")
Return SetError(3, 0, False)
EndIf
; ── 4. OZ-Schema ermitteln ───────────────────────────────
; Bestimme Typ und maximale Segmentlängen
Local $sOZTyp = "A"
Local $iMaxL1 = 2
Local $iMaxL2 = 2
Local $iMaxL3 = 2
Local $iMaxPos = 4
For $i = 0 To $iPosN - 1
Local $aOZ = _OZInfo($aPosData[$i][0])
If $aOZ[0] = "B" Then $sOZTyp = "B"
If $aOZ[0] = "C" And $sOZTyp = "A" Then $sOZTyp = "C"
If $aOZ[6] > $iMaxL1 Then $iMaxL1 = $aOZ[6]
If $aOZ[7] > $iMaxL2 Then $iMaxL2 = $aOZ[7]
If $aOZ[8] > $iMaxL3 Then $iMaxL3 = $aOZ[8]
If $aOZ[9] > $iMaxPos Then $iMaxPos = $aOZ[9]
Next
; ── 5. OZ-Hierarchie aufbauen ────────────────────────────
Local $aTitel[100]
Local $iTitelN = 0
Local $aUTitel[1000][3] ; [n][0]=E1 [n][1]=E2 [n][2]=E3(nur Typ B)
Local $iUTitelN = 0
For $i = 0 To $iPosN - 1
Local $aOZI = _OZInfo($aPosData[$i][0])
Local $sE1 = $aOZI[1]
Local $sE2 = $aOZI[2]
Local $sE3 = $aOZI[3]
; Titel (Ebene 1)
If $sE1 <> "" Then
Local $bGefT = False
For $j = 0 To $iTitelN - 1
If $aTitel[$j] = $sE1 Then
$bGefT = True
ExitLoop
EndIf
Next
If Not $bGefT Then
$aTitel[$iTitelN] = $sE1
$iTitelN += 1
EndIf
EndIf
; Untertitel (Ebene 2 + ggf. Ebene 3)
If $sE1 <> "" Then
Local $bGefU = False
For $j = 0 To $iUTitelN - 1
If $aUTitel[$j][0] = $sE1 And $aUTitel[$j][1] = $sE2 And $aUTitel[$j][2] = $sE3 Then
$bGefU = True
ExitLoop
EndIf
Next
If Not $bGefU Then
$aUTitel[$iUTitelN][0] = $sE1
$aUTitel[$iUTitelN][1] = $sE2
$aUTitel[$iUTitelN][2] = $sE3
$iUTitelN += 1
EndIf
EndIf
Next
; ── 6. Zeit ──────────────────────────────────────────────
Local $sTime = StringRight("0" & @HOUR, 2) & ":" & StringRight("0" & @MIN, 2) & ":" & StringRight("0" & @SEC, 2)
; ── 7. XML aufbauen ──────────────────────────────────────
Local $sX = ""
$sX &= '<?xml version="1.0" encoding="UTF-8"?>' & @CRLF
$sX &= '<!-- REB 23.003 (2009) - X31 Export AutoIt v5 -->' & @CRLF
$sX &= '<GAEB xmlns="http://www.gaeb.de/GAEB_DA_XML/DA31/3.3" xmlns:BVBS="BVBS">' & @CRLF
; GAEBInfo
$sX &= '<GAEBInfo>'
$sX &= '<Version>3.3</Version>'
$sX &= '<VersDate>2023-01</VersDate>'
$sX &= '<Date>' & $sISODatum & '</Date>'
$sX &= '<Time>' & $sTime & '</Time>'
$sX &= '<ProgSystem>AutoIt REB Engine V1.2</ProgSystem>'
$sX &= '<ProgName>AutoIt REB X31 Export</ProgName>'
$sX &= '</GAEBInfo>' & @CRLF
; QtyDeterm
$sX &= '<QtyDeterm>' & @CRLF
; PrjInfo
$sX &= '<PrjInfo>'
$sX &= '<RefPrjName>' & _XE($sBaustelle) & '</RefPrjName>'
$sX &= '<RefPrjID>' & _XE($sLVName) & '</RefPrjID>'
$sX &= '</PrjInfo>' & @CRLF
; QtyDetermInfo
$sX &= '<QtyDetermInfo ID="' & $sUID & '">'
$sX &= '<MethodDescription>REB23003-2009</MethodDescription>'
If $sStartISO <> "" Then $sX &= '<PeriodFrom>' & $sStartISO & '</PeriodFrom>'
If $sEndISO <> "" Then $sX &= '<PeriodTo>' & $sEndISO & '</PeriodTo>'
$sX &= '</QtyDetermInfo>' & @CRLF
; DP
$sX &= '<DP>31</DP>' & @CRLF
; OWN (leer, aber Pflichtfeld)
$sX &= '<OWN><Address><Name1>' & _XE($sAspaN) & '</Name1><Name2></Name2>'
$sX &= '<Name3/><Name4/><Street></Street><PCode></PCode><City></City>'
$sX &= '<Contact/><Phone>' & _XE($sAspaTel) & '</Phone><Fax/><Email/>'
$sX &= '</Address></OWN>' & @CRLF
; CTR (leer, aber Pflichtfeld)
$sX &= '<CTR><Address><Name1/><Name2></Name2><Name3/><Name4/>'
$sX &= '<Street></Street><PCode></PCode><City></City>'
$sX &= '<Contact/><Phone/><Fax/><Email/></Address></CTR>' & @CRLF
; BoQ
$sX &= '<BoQ ID="DF_' & _NextID() & '">' & @CRLF
$sX &= '<RefBoQName>' & _XE($sLVName) & '</RefBoQName>'
$sX &= '<RefBoQID>' & $sBoQUID & '</RefBoQID>' & @CRLF
; BoQBkdn exakt wie funktionierende alte Version
; WICHTIG: <Num>No</Num> und <LblBoQBkdn> sind Pflicht für MWM/Dataflor!
Select
Case $sOZTyp = "A"
$sX &= '<BoQBkdn><Type>BoQLevel</Type><LblBoQBkdn>Titel</LblBoQBkdn><Length>' & $iMaxL1 & '</Length><Num>No</Num></BoQBkdn>'
$sX &= '<BoQBkdn><Type>BoQLevel</Type><LblBoQBkdn>Bauteil</LblBoQBkdn><Length>' & $iMaxL2 & '</Length><Num>No</Num></BoQBkdn>'
$sX &= '<BoQBkdn><Type>Item</Type><LblBoQBkdn>Position</LblBoQBkdn><Length>' & $iMaxPos & '</Length><Num>No</Num></BoQBkdn>'
$sX &= '<BoQBkdn><Type>Index</Type><LblBoQBkdn>Index</LblBoQBkdn><Length>1</Length><Num>No</Num></BoQBkdn>'
Case $sOZTyp = "B"
$sX &= '<BoQBkdn><Type>BoQLevel</Type><LblBoQBkdn>Titel</LblBoQBkdn><Length>' & $iMaxL1 & '</Length><Num>No</Num></BoQBkdn>'
$sX &= '<BoQBkdn><Type>BoQLevel</Type><LblBoQBkdn>Bauteil</LblBoQBkdn><Length>' & $iMaxL2 & '</Length><Num>No</Num></BoQBkdn>'
$sX &= '<BoQBkdn><Type>BoQLevel</Type><LblBoQBkdn>Gewerk</LblBoQBkdn><Length>' & $iMaxL3 & '</Length><Num>No</Num></BoQBkdn>'
$sX &= '<BoQBkdn><Type>Item</Type><LblBoQBkdn>Position</LblBoQBkdn><Length>' & $iMaxPos & '</Length><Num>No</Num></BoQBkdn>'
$sX &= '<BoQBkdn><Type>Index</Type><LblBoQBkdn>Index</LblBoQBkdn><Length>1</Length><Num>No</Num></BoQBkdn>'
Case $sOZTyp = "C"
$sX &= '<BoQBkdn><Type>BoQLevel</Type><LblBoQBkdn>Titel</LblBoQBkdn><Length>2</Length><Num>No</Num></BoQBkdn>'
$sX &= '<BoQBkdn><Type>Item</Type><LblBoQBkdn>Position</LblBoQBkdn><Length>' & $iMaxPos & '</Length><Num>No</Num></BoQBkdn>'
$sX &= '<BoQBkdn><Type>Index</Type><LblBoQBkdn>Index</LblBoQBkdn><Length>1</Length><Num>No</Num></BoQBkdn>'
EndSelect
$sX &= @CRLF
; Ctlg (Pflichtfeld in alter Version)
$sX &= '<Ctlg><CtlgID>idDIN276_1993</CtlgID>'
$sX &= '<CtlgType>cost group DIN 276-93</CtlgType>'
$sX &= '<CtlgName>DIN 276-93</CtlgName></Ctlg>' & @CRLF
; BoQBody
$sX &= '<BoQBody>' & @CRLF
; OZ-Zähler für QTakeoff-Blattadressen
Local $iOZCnt = 0
; Typ C: Flachliste unter einer Sammelgruppe
If $sOZTyp = "C" Then
$sX &= '<BoQCtgy RNoPart="' & _XE($sBauabs) & '" ID="DF_' & _NextID() & '">' & @CRLF
$sX &= '<BoQBody><Itemlist>' & @CRLF
For $i = 0 To $iPosN - 1
Local $aOZC = _OZInfo($aPosData[$i][0])
$sX &= _BuildItem($aPosData[$i], $aOZC[4], $iOZCnt)
Next
$sX &= '</Itemlist></BoQBody>' & @CRLF
$sX &= '</BoQCtgy>' & @CRLF
; Typ A: 2 Ebenen
ElseIf $sOZTyp = "A" Then
For $t = 0 To $iTitelN - 1
Local $sT = $aTitel[$t]
$sX &= '<BoQCtgy RNoPart="' & _XE($sT) & '" ID="DF_' & _NextID() & '">' & @CRLF
$sX &= '<BoQBody>' & @CRLF
For $u = 0 To $iUTitelN - 1
If $aUTitel[$u][0] <> $sT Then ContinueLoop
Local $sU = $aUTitel[$u][1]
$sX &= '<BoQCtgy RNoPart="' & _XE($sU) & '" ID="DF_' & _NextID() & '">' & @CRLF
$sX &= '<BoQBody><Itemlist>' & @CRLF
For $p = 0 To $iPosN - 1
Local $aOZA = _OZInfo($aPosData[$p][0])
If $aOZA[1] <> $sT Or $aOZA[2] <> $sU Then ContinueLoop
$sX &= _BuildItem($aPosData[$p], $aOZA[4], $iOZCnt)
Next
$sX &= '</Itemlist></BoQBody>' & @CRLF
$sX &= '</BoQCtgy>' & @CRLF
Next
$sX &= '</BoQBody>' & @CRLF
$sX &= '</BoQCtgy>' & @CRLF
Next
; Typ B: 3 Ebenen
ElseIf $sOZTyp = "B" Then
For $t = 0 To $iTitelN - 1
Local $sTB = $aTitel[$t]
$sX &= '<BoQCtgy RNoPart="' & _XE($sTB) & '" ID="DF_' & _NextID() & '">' & @CRLF
$sX &= '<BoQBody>' & @CRLF
; Ebene 2
Local $aE2Seen[100]
Local $iE2N = 0
For $u = 0 To $iUTitelN - 1
If $aUTitel[$u][0] <> $sTB Then ContinueLoop
Local $sE2 = $aUTitel[$u][1]
; Ebene 2 nur einmal öffnen
Local $bE2New = True
For $x = 0 To $iE2N - 1
If $aE2Seen[$x] = $sE2 Then
$bE2New = False
ExitLoop
EndIf
Next
If $bE2New Then
If $iE2N > 0 Then
; Vorherige Ebene 2 schliessen
$sX &= '</BoQBody></BoQCtgy>' & @CRLF
EndIf
$aE2Seen[$iE2N] = $sE2
$iE2N += 1
$sX &= '<BoQCtgy RNoPart="' & _XE($sE2) & '" ID="DF_' & _NextID() & '">' & @CRLF
$sX &= '<BoQBody>' & @CRLF
EndIf
; Ebene 3
Local $sE3 = $aUTitel[$u][2]
$sX &= '<BoQCtgy RNoPart="' & _XE($sE3) & '" ID="DF_' & _NextID() & '">' & @CRLF
$sX &= '<BoQBody><Itemlist>' & @CRLF
For $p = 0 To $iPosN - 1
Local $aOZB = _OZInfo($aPosData[$p][0])
If $aOZB[1] <> $sTB Or $aOZB[2] <> $sE2 Or $aOZB[3] <> $sE3 Then ContinueLoop
$sX &= _BuildItem($aPosData[$p], $aOZB[4], $iOZCnt)
Next
$sX &= '</Itemlist></BoQBody>' & @CRLF
$sX &= '</BoQCtgy>' & @CRLF
Next
If $iE2N > 0 Then
$sX &= '</BoQBody></BoQCtgy>' & @CRLF
EndIf
$sX &= '</BoQBody>' & @CRLF
$sX &= '</BoQCtgy>' & @CRLF
Next
EndIf
$sX &= '</BoQBody>' & @CRLF
$sX &= '</BoQ>' & @CRLF
$sX &= '</QtyDeterm>' & @CRLF
$sX &= '</GAEB>'
; ── 8. Als echtes UTF-8 mit BOM speichern ────────────────
; BOM = EF BB BF, dann UTF-8 Bytes
; StringToBinary($s, 4) = UTF-8 Kodierung
If FileExists($sAusgabeX31) Then FileDelete($sAusgabeX31)
Local $binBOM = Binary("0xEFBBBF")
Local $binBody = StringToBinary($sX, 4)
Local $binFull = $binBOM & $binBody
Local $bOK = FileWrite($sAusgabeX31, $binFull)
If Not $bOK Or Not FileExists($sAusgabeX31) Then
MsgBox(16, "Fehler", "Ausgabedatei konnte nicht geschrieben werden:" & @CRLF & $sAusgabeX31)
Return SetError(4, 0, False)
EndIf
MsgBox(64, "X31 Export", "X31-Datei erfolgreich erstellt!" & @CRLF & @CRLF & _
"Datei: " & $sAusgabeX31 & @CRLF & _
"Positionen: " & $iPosN & @CRLF & _
"OZ-Typ: " & $sOZTyp & @CRLF & _
"Format: GAEB DA XML 3.3 / DA31 / REB 23.003")
Return True
EndFunc
; ==============================================================================
; _BuildItem Einzelnes <Item>-Element erzeugen
; Exakt nach funktionierender alter Struktur:
; <Item>
; <QtyDeterm>
; <Qty>N.NNN</Qty>
; <QDetermItem>
; <QTakeoff Row="K-Zeile"/>
; <BVBS:Explanation>...</BVBS:Explanation>
; </QDetermItem>
; <QDetermItem>
; <QTakeoff Row="L-Zeile"/>
; </QDetermItem>
; </QtyDeterm>
; </Item>
; ==============================================================================
Func _BuildItem(ByRef $aPos, $sPosNr, ByRef $iOZCnt)
Local $fQty = $aPos[1]
Local $sOrt = $aPos[2]
Local $sBeschr = $aPos[3]
Local $sBemerk = $aPos[4]
Local $sFaktor = $aPos[7]
Local $sEinzel = $aPos[8]
; Qty formatieren (3 Dezimalstellen)
Local $sQty = _FmtQty($fQty)
; OZ-Codes für K- und L-Zeile
Local $sOZ1 = _REBOZCode($iOZCnt)
$iOZCnt += 1
Local $sOZ2 = _REBOZCode($iOZCnt)
$iOZCnt += 1
; Formel für L-Zeile: "100091" + Qty
; z.B. "1000912.771=" entspricht Formel 91 mit Wert 2.771
Local $sFormel = "100091" & $sQty
; K-Zeilen-Text: bevorzuge Bemerkung, dann Ort
Local $sKText = $sBemerk
If $sKText = "" Then $sKText = $sOrt
If $sKText = "" Then $sKText = $sBeschr
Local $sItem = ""
$sItem &= '<Item ID="DF_' & _NextID() & '" RNoPart="' & _XE($sPosNr) & '">'
$sItem &= '<QtyDeterm>'
$sItem &= '<Qty>' & $sQty & '</Qty>'
; K-Zeile (Kommentarzeile mit Bezeichnung)
$sItem &= '<QDetermItem>'
$sItem &= '<QTakeoff Row="' & _XE(_KZeile($sKText, $sOZ1)) & '"/>'
$sItem &= '<BVBS:Explanation>' & _XE(StringStripWS($sBeschr, 3)) & '</BVBS:Explanation>'
$sItem &= '</QDetermItem>'
; L-Zeile (Mengenzeile mit Formel 91)
$sItem &= '<QDetermItem>'
$sItem &= '<QTakeoff Row="' & _XE(_LZeile($sFormel, $sOZ2)) & '"/>'
$sItem &= '</QDetermItem>'
$sItem &= '</QtyDeterm>'
$sItem &= '</Item>' & @CRLF
Return $sItem
EndFunc
; ==============================================================================
; Hilfsfunktionen
; ==============================================================================
; K-Zeile: 12sp + * + Bezeichnung(56) + OZCode(6) + 5sp = 80 Zeichen
Func _KZeile($sBez, $sOZCode)
Local $sBezPad = StringLeft($sBez & " ", 56)
Return " *" & $sBezPad & $sOZCode & " "
EndFunc
; L-Zeile: 25sp + Formel"="(44) + OZCode(6) + 5sp = 80 Zeichen
Func _LZeile($sFormel, $sOZCode)
Local $sF = StringLeft($sFormel & "=" & " ", 44)
Return " " & $sF & $sOZCode & " "
EndFunc
; REB OZ-Code aus Zähler: 0-99 → "1000B0"-Format, dann Buchstaben
Func _REBOZCode($iIdx)
If $iIdx < 100 Then
Local $s = String($iIdx)
While StringLen($s) < 3
$s = "0" & $s
WEnd
Return "100" & $s
EndIf
Local $iLIdx = $iIdx - 100
Local $sABC = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
Local $sLet = ""
Local $iWork = $iLIdx
Do
$sLet = StringMid($sABC, Mod($iWork, 26) + 1, 1) & $sLet
$iWork = Int($iWork / 26) - 1
Until $iWork < 0
If StringLen($sLet) > 2 Then $sLet = StringRight($sLet, 2)
Return "100" & $sLet & "0"
EndFunc
; Qty auf 3 Dezimalstellen formatieren
Func _FmtQty($fVal)
; Manuell formatieren ohne StringFormat
Local $iGanz = Int($fVal)
Local $fDez = $fVal - $iGanz
If $fDez < 0 Then $fDez = -$fDez
Local $iD3 = Int($fDez * 1000 + 0.5)
Local $sDez = String($iD3)
While StringLen($sDez) < 3
$sDez = "0" & $sDez
WEnd
Return String($iGanz) & "." & $sDez
EndFunc
; OZ analysieren
Func _OZInfo($sOZ)
Local $aI[10]
$aI[0] = "?"
$aI[1] = ""
$aI[2] = ""
$aI[3] = ""
$aI[4] = $sOZ
$aI[5] = $sOZ
$aI[6] = 0
$aI[7] = 0
$aI[8] = 0
$aI[9] = 0
If $sOZ = "" Then Return $aI
If StringRegExp($sOZ, "^\d{6,10}$") Then
$aI[0] = "C"
$aI[4] = $sOZ
$aI[9] = StringLen($sOZ)
Return $aI
EndIf
Local $aS = StringSplit($sOZ, ".", $STR_NOCOUNT)
Local $n = UBound($aS)
If $n = 3 Then
$aI[0] = "A"
$aI[1] = $aS[0]
$aI[6] = StringLen($aS[0])
$aI[2] = $aS[1]
$aI[7] = StringLen($aS[1])
$aI[4] = $aS[2]
$aI[9] = StringLen($aS[2])
ElseIf $n = 4 Then
$aI[0] = "B"
$aI[1] = $aS[0]
$aI[6] = StringLen($aS[0])
$aI[2] = $aS[1]
$aI[7] = StringLen($aS[1])
$aI[3] = $aS[2]
$aI[8] = StringLen($aS[2])
$aI[4] = $aS[3]
$aI[9] = StringLen($aS[3])
EndIf
$aI[5] = $sOZ
Return $aI
EndFunc
; OZ-Typ prüfen
Func _IsValidOZ($sOZ)
If $sOZ = "" Then Return False
If StringRegExp($sOZ, "^\d{1,4}\.\d{1,4}\.\d{1,6}$") Then Return True
If StringRegExp($sOZ, "^\d{1,4}\.\d{1,4}\.\d{1,4}\.\d{1,6}$") Then Return True
If StringRegExp($sOZ, "^\d{6,10}$") Then Return True
Return False
EndFunc
; Kopfdaten-Wert lesen
Func _KopfGet(ByRef $aKopf, $sKey)
For $i = 0 To UBound($aKopf) - 1
If $aKopf[$i][0] = $sKey Then Return $aKopf[$i][1]
Next
Return ""
EndFunc
; XML-Sonderzeichen kodieren
Func _XE($s)
$s = StringReplace($s, "&", "&amp;")
$s = StringReplace($s, "<", "&lt;")
$s = StringReplace($s, ">", "&gt;")
$s = StringReplace($s, '"', "&quot;")
$s = StringReplace($s, "'", "&apos;")
Return $s
EndFunc
; String zu Float (Komma→Punkt)
Func _ToFloat($s)
If $s = "" Then Return 0
Return Number(StringReplace(StringStripWS($s, 3), ",", "."))
EndFunc
; Datum DD.MM.YYYY → YYYY-MM-DD
Func _DatumISO($s)
$s = StringStripWS($s, 3)
If StringRegExp($s, "^\d{2}\.\d{2}\.\d{4}$") Then
Local $aD = StringSplit($s, ".", $STR_NOCOUNT)
Return $aD[2] & "-" & $aD[1] & "-" & $aD[0]
EndIf
Return $s
EndFunc
; Fortlaufende DF_-ID
Func _NextID()
$g_iIDCnt += 1
Return $g_iIDCnt
EndFunc
; Pseudo-UUID
Func _UUID()
Local $sHex = "0123456789abcdef"
Local $sUID = ""
Local $aLen[5]
$aLen[0] = 8
$aLen[1] = 4
$aLen[2] = 4
$aLen[3] = 4
$aLen[4] = 12
For $i = 0 To 4
If $i > 0 Then $sUID &= "-"
For $j = 1 To $aLen[$i]
$sUID &= StringMid($sHex, Random(1, 16, 1), 1)
Next
Next
Return $sUID
EndFunc