*-- sync-comenzi-web.prg - Orchestrator pentru sincronizarea comenzilor web cu Oracle ROA
*-- Autor: Claude AI
*-- Data: 10 septembrie 2025
*-- Dependency: gomag-vending.prg trebuie rulat mai intai pentru generarea JSON-urilor
Set Safety Off
Set Century On
Set Date Dmy
Set Exact On
Set Ansi On
Set Deleted On
*-- Variabile globale
Private gcAppPath, gcLogFile, gnStartTime, gnOrdersProcessed, gnOrdersSuccess, gnOrdersErrors, gcFailedSKUs
Private goConnectie, goSettings, goAppSetup, gcStepError
Local lcJsonPattern, laJsonFiles[1], lnJsonFiles, lnIndex, lcJsonFile
Local loJsonData, lcJsonContent, lnOrderCount, lnOrderIndex
Local loOrder, lcResult, llProcessSuccess, lcPath
goConnectie = Null
gcStepError = ""
*-- Initializare
gcAppPath = Addbs(Justpath(Sys(16,0)))
Set Default To (m.gcAppPath)
lcPath = gcAppPath + 'nfjson;'
Set Path To &lcPath Additive
Set Procedure To utils.prg Additive
Set Procedure To ApplicationSetup.prg Additive
Set Procedure To nfjsonread.prg Additive
Set Procedure To nfjsoncreate.prg Additive
Set Procedure To regex.prg Additive
*-- Statistici
gnStartTime = Seconds()
gnOrdersProcessed = 0
gnOrdersSuccess = 0
gnOrdersErrors = 0
gcFailedSKUs = ""
*-- Initializare logging
gcLogFile = InitLog("sync_comenzi")
*-- Creare si initializare clasa setup aplicatie
goAppSetup = Createobject("ApplicationSetup", gcAppPath)
If !goAppSetup.Initialize()
LogMessage("EROARE: Setup-ul aplicatiei a esuat sau necesita configurare!", "ERROR", gcLogFile)
Return .F.
Endif
goSettings = goAppSetup.GetSettings()
*-- Verificare directoare necesare
If !Directory(gcAppPath + "output")
LogMessage("EROARE: Directorul output/ nu exista!", "ERROR", gcLogFile)
Return .F.
Endif
*-- Rulare automata adapter pentru obtinere comenzi
If goSettings.AutoRunAdapter
If !ExecuteAdapter()
LogMessage("EROARE adapter, continuez cu JSON existente", "WARN", gcLogFile)
Endif
Endif
*-- Gasire fisiere JSON comenzi
lcJsonPattern = gcAppPath + "output\" + goSettings.JsonFilePattern
lnJsonFiles = Adir(laJsonFiles, lcJsonPattern)
If lnJsonFiles = 0
LogMessage("Nu au fost gasite fisiere JSON cu comenzi web", "WARN", gcLogFile)
Return .T.
Endif
*-- Conectare Oracle
If !ConnectToOracle()
LogMessage("EROARE: Nu s-a putut conecta la Oracle ROA", "ERROR", gcLogFile)
Return .F.
Endif
*-- Header compact
LogMessage("SYNC START | " + goSettings.OracleDSN + " " + goSettings.OracleUser + " | " + Transform(lnJsonFiles) + " JSON files", "INFO", gcLogFile)
*-- Procesare fiecare fisier JSON gasit
For lnIndex = 1 To lnJsonFiles
lcJsonFile = gcAppPath + "output\" + laJsonFiles[lnIndex, 1]
Try
lcJsonContent = Filetostr(lcJsonFile)
If Empty(lcJsonContent)
Loop
Endif
loJsonData = nfjsonread(lcJsonContent)
If Isnull(loJsonData) Or Type('loJsonData') != 'O' Or Type('loJsonData.orders') != 'O'
LogMessage("EROARE JSON: " + laJsonFiles[lnIndex, 1], "ERROR", gcLogFile)
Loop
Endif
Local Array laOrderProps[1]
lnOrderCount = Amembers(laOrderProps, loJsonData.orders, 0)
*-- Procesare fiecare comanda
For lnOrderIndex = 1 To lnOrderCount
Local lcOrderId, loOrder
lcOrderId = laOrderProps[lnOrderIndex]
loOrder = Evaluate('loJsonData.orders.' + lcOrderId)
If Type('loOrder') = 'O'
gnOrdersProcessed = gnOrdersProcessed + 1
llProcessSuccess = ProcessWebOrder(loOrder, lnOrderIndex, lnOrderCount)
If llProcessSuccess
gnOrdersSuccess = gnOrdersSuccess + 1
Else
gnOrdersErrors = gnOrdersErrors + 1
Endif
Endif
If m.gnOrdersErrors > 10
Exit
Endif
Endfor
Catch To loError
LogMessage("EROARE fisier " + laJsonFiles[lnIndex, 1] + ": " + loError.Message, "ERROR", gcLogFile)
gnOrdersErrors = gnOrdersErrors + 1
Endtry
If m.gnOrdersErrors > 10
LogMessage("Peste 10 erori, stop import", "ERROR", gcLogFile)
Exit
Endif
Endfor
*-- Inchidere conexiune Oracle
DisconnectFromOracle()
*-- Sumar SKU-uri lipsa
If !Empty(gcFailedSKUs)
LogMessage("=== SKU-URI LIPSA ===", "INFO", gcLogFile)
Local lnSkuCount, lnSkuIdx
Local Array laSkus[1]
lnSkuCount = Alines(laSkus, gcFailedSKUs, .T., CHR(10))
For lnSkuIdx = 1 To lnSkuCount
If !Empty(laSkus[lnSkuIdx])
LogMessage(Alltrim(laSkus[lnSkuIdx]), "INFO", gcLogFile)
Endif
Endfor
LogMessage("=== SFARSIT SKU-URI LIPSA ===", "INFO", gcLogFile)
Endif
*-- Footer compact
Local lcStopped, lnSkuTotal, lnDuration
lnDuration = Int(Seconds() - gnStartTime)
lnSkuTotal = Iif(Empty(gcFailedSKUs), 0, Occurs(CHR(10), gcFailedSKUs) + 1)
lcStopped = Iif(gnOrdersErrors > 10, " (stopped early)", "")
LogMessage("SYNC END | " + Transform(gnOrdersProcessed) + " processed: " + Transform(gnOrdersSuccess) + " ok, " + Transform(gnOrdersErrors) + " err" + lcStopped + " | " + Transform(lnSkuTotal) + " SKUs lipsa | " + Transform(lnDuration) + "s", "INFO", gcLogFile)
CloseLog(gnStartTime, 0, gnOrdersProcessed, gcLogFile)
Return .T.
*-- ===================================================================
*-- HELPER FUNCTIONS
*-- ===================================================================
*-- Conectare la Oracle
Function ConnectToOracle
Local llSuccess, lnHandle
llSuccess = .F.
Try
lnHandle = SQLConnect(goSettings.OracleDSN, goSettings.OracleUser, goSettings.OraclePassword)
If lnHandle > 0
goConnectie = lnHandle
llSuccess = .T.
Else
LogMessage("EROARE conectare Oracle: Handle=" + Transform(lnHandle), "ERROR", gcLogFile)
Endif
Catch To loError
LogMessage("EROARE conectare Oracle: " + loError.Message, "ERROR", gcLogFile)
Endtry
Return llSuccess
Endfunc
*-- Deconectare de la Oracle
Function DisconnectFromOracle
If Type('goConnectie') = 'N' And goConnectie > 0
SQLDisconnect(goConnectie)
Endif
Return .T.
Endfunc
*-- Procesare comanda web - logeaza O SINGURA LINIE per comanda
*-- Format: [N/Total] OrderNumber P:PartnerID A:AddrFact/AddrLivr -> OK/ERR details
Function ProcessWebOrder
Lparameters loOrder, tnIndex, tnTotal
Local llSuccess, lcOrderNumber, lcOrderDate, lnPartnerID, lcArticlesJSON
Local lcSQL, lnResult, lcErrorDetails, lnIdComanda, llSucces
Local ldOrderDate, loError
Local lnIdAdresaFacturare, lnIdAdresaLivrare
Local lcPrefix, lcSummary, lcErrDetail
lnIdAdresaLivrare = NULL
lnIdAdresaFacturare = NULL
lnIdComanda = 0
llSucces = .T.
lnPartnerID = 0
lcOrderNumber = "?"
*-- Prefix: [N/Total] OrderNumber
lcPrefix = "[" + Transform(tnIndex) + "/" + Transform(tnTotal) + "]"
Try
*-- Validare comanda
If !ValidateWebOrder(loOrder)
LogMessage(lcPrefix + " ? -> ERR VALIDARE: date obligatorii lipsa", "ERROR", gcLogFile)
Return .F.
Endif
*-- Extragere date comanda
lcOrderNumber = CleanWebText(Transform(loOrder.Number))
lcOrderDate = ConvertWebDate(loOrder.Date)
ldOrderDate = String2Date(m.lcOrderDate, 'yyyymmdd')
lcPrefix = lcPrefix + " " + lcOrderNumber
*-- Procesare partener
gcStepError = ""
lnPartnerID = ProcessPartner(loOrder.billing)
If lnPartnerID <= 0
LogMessage(lcPrefix + " -> ERR PARTENER: " + Iif(Empty(gcStepError), "nu s-a putut procesa", gcStepError), "ERROR", gcLogFile)
Return .F.
Endif
*-- Adrese
lnIdAdresaFacturare = ProcessAddress(m.lnPartnerID, loOrder.billing)
If Type('loOrder.shipping') = 'O'
lnIdAdresaLivrare = ProcessAddress(m.lnPartnerID, loOrder.shipping)
Endif
*-- Construire JSON articole
lcArticlesJSON = BuildArticlesJSON(loOrder.items)
If Empty(m.lcArticlesJSON)
LogMessage(lcPrefix + " P:" + Transform(lnPartnerID) + " -> ERR JSON_ARTICOLE", "ERROR", gcLogFile)
Return .F.
Endif
*-- Import comanda in Oracle
lcSQL = "BEGIN PACK_IMPORT_COMENZI.importa_comanda(?lcOrderNumber, ?ldOrderDate, ?lnPartnerID, ?lcArticlesJSON, ?lnIdAdresaLivrare, ?lnIdAdresaFacturare, ?goSettings.IdPol, ?goSettings.IdSectie, ?@lnIdComanda); END;"
lnResult = SQLExec(goConnectie, lcSQL)
*-- Construire linie sumar cu ID-uri adrese
lcSummary = lcPrefix + " P:" + Transform(lnPartnerID) + ;
" A:" + Transform(Nvl(lnIdAdresaFacturare, 0)) + "/" + Transform(Nvl(lnIdAdresaLivrare, 0))
If lnResult > 0 And Nvl(m.lnIdComanda, 0) > 0
LogMessage(lcSummary + " -> OK ID:" + Transform(m.lnIdComanda), "INFO", gcLogFile)
Return .T.
Else
lcErrorDetails = GetOracleErrorDetails()
lcErrDetail = ClassifyImportError(lcErrorDetails)
CollectFailedSKUs(lcErrorDetails)
LogMessage(lcSummary + " -> ERR " + lcErrDetail, "ERROR", gcLogFile)
Return .F.
Endif
Catch To loError
LogMessage(lcPrefix + " -> ERR EXCEPTIE: " + loError.Message, "ERROR", gcLogFile)
Return .F.
Endtry
Endfunc
*-- Validare comanda web
Function ValidateWebOrder
Parameters loOrder
Local llValid
llValid = .T.
If Type('loOrder.number') != 'C' Or Empty(loOrder.Number)
llValid = .F.
Endif
If Type('loOrder.date') != 'C' Or Empty(loOrder.Date)
llValid = .F.
Endif
If Type('loOrder.billing') != 'O'
llValid = .F.
Endif
If Type('loOrder.items') != 'O'
llValid = .F.
Endif
Return llValid
Endfunc
*-- Procesare partener (fara logging, seteaza gcStepError la eroare)
Function ProcessPartner
Lparameters toBilling
Local lcDenumire, lcCodFiscal, lcRegistru, lcAdresa, lcTelefon, lcEmail, lcRegistru
Local lcSQL, lnResult
Local lnIdPart, lnIdAdresa, lnIsPersoanaJuridica
lnIdPart = 0
lnIdAdresa = 0
lcDenumire = ''
lcCodFiscal = ''
lcRegistru = ''
lnIsPersoanaJuridica = 0
lcCodFiscal = Null
Try
If Type('toBilling.company') = 'O' And !Empty(toBilling.company.Name)
loCompany = toBilling.company
lcDenumire = CleanWebText(loCompany.Name)
lcCodFiscal = Iif(Type('loCompany.code') = 'C', loCompany.Code, Null)
lcCodFiscal = CleanWebText(m.lcCodFiscal)
lcRegistru = Iif(Type('loCompany.registrationNo') = 'C', loCompany.registrationNo, Null)
lcRegistru = CleanWebText(m.lcRegistru)
lnIsPersoanaJuridica = 1
Else
If Type('toBilling.firstname') = 'C'
lcDenumire = CleanWebText(Alltrim(toBilling.firstname) + " " + Alltrim(toBilling.lastname))
lnIsPersoanaJuridica = 0
Endif
Endif
lcSQL = "BEGIN PACK_IMPORT_PARTENERI.cauta_sau_creeaza_partener(?lcCodFiscal, ?lcDenumire, ?lcRegistru, ?lnIsPersoanaJuridica, ?@lnIdPart); END;"
lnResult = SQLExec(goConnectie, lcSQL)
If lnResult <= 0
gcStepError = lcDenumire + " | " + GetOracleErrorDetails()
Endif
Catch To loError
gcStepError = loError.Message
Endtry
Return m.lnIdPart
Endfunc
*-- Procesare adresa (fara logging)
Function ProcessAddress
Lparameters tnIdPart, toAdresa
Local lcAdresa, lcTelefon, lcEmail, lcSQL, lnResult, lnIdAdresa
lnIdAdresa = 0
Try
If !Empty(Nvl(m.tnIdPart, 0))
lcAdresa = FormatAddressForOracle(toAdresa)
lcTelefon = Iif(Type('toAdresa.phone') = 'C', toAdresa.phone, "")
lcEmail = Iif(Type('toAdresa.email') = 'C', toAdresa.email, "")
lcSQL = "BEGIN PACK_IMPORT_PARTENERI.cauta_sau_creeaza_adresa(?tnIdPart, ?lcAdresa, ?lcTelefon, ?lcEmail, ?@lnIdAdresa); END;"
lnResult = SQLExec(goConnectie, lcSQL)
Endif
Catch To loError
Endtry
Return m.lnIdAdresa
Endfunc
*-- Construire JSON articole
Function BuildArticlesJSON
Lparameters loItems
Local lcJSON, loError
lcJSON = ""
Try
lcJSON = nfjsoncreate(loItems)
Catch To loError
lcJSON = ""
Endtry
Return lcJSON
Endfunc
*-- Curatare text web (HTML entities -> ASCII simplu)
Function CleanWebText
Parameters tcText
Local lcResult
If Empty(tcText) Or Type('tcText') != 'C'
Return ""
Endif
lcResult = tcText
lcResult = Strtran(lcResult, 'ă', 'a')
lcResult = Strtran(lcResult, 'ș', 's')
lcResult = Strtran(lcResult, 'ț', 't')
lcResult = Strtran(lcResult, 'î', 'i')
lcResult = Strtran(lcResult, 'â', 'a')
lcResult = Strtran(lcResult, '&', '&')
lcResult = Strtran(lcResult, '<', '<')
lcResult = Strtran(lcResult, '>', '>')
lcResult = Strtran(lcResult, '"', '"')
lcResult = Strtran(lcResult, '
', ' ')
lcResult = Strtran(lcResult, '
', ' ')
lcResult = Strtran(lcResult, '
', ' ')
lcResult = Strtran(lcResult, '\/', '/')
Return Alltrim(lcResult)
Endfunc
*-- Conversie data web in format YYYYMMDD
Function ConvertWebDate
Parameters tcWebDate
Local lcResult
If Empty(tcWebDate) Or Type('tcWebDate') != 'C'
Return Dtos(Date())
Endif
lcResult = Strtran(Left(tcWebDate, 10), "-", "",1,10,1)
If Len(lcResult) = 8
Return lcResult
Else
Return Dtos(Date())
Endif
Endfunc
*-- Conversie string in Date
Function String2Date
Lparameters tcDate, tcFormat
Local lcAn, lcDate, lcFormat, lcLuna, lcZi, ldData, lnAn, lnLuna, lnZi, loEx
ldData = {}
lcDate = m.tcDate
lcFormat = Iif(!Empty(m.tcFormat), Alltrim(Lower(m.tcFormat)), 'yyyymmdd')
lcDate = Chrtran(m.lcDate, '-/\','...')
lcDate = Strtran(m.lcDate, '.', '', 1, 2, 1)
lcFormat = Chrtran(m.lcFormat, '.-/\','...')
lcFormat = Strtran(m.lcFormat, '.', '', 1, 2, 1)
Do Case
Case m.lcFormat = 'ddmmyyyy'
lcAn = Substr(m.tcDate, 5, 4)
lcLuna = Substr(m.tcDate, 3, 2)
lcZi = Substr(m.tcDate, 1, 2)
Otherwise
lcAn = Substr(m.tcDate, 1, 4)
lcLuna = Substr(m.tcDate, 5, 2)
lcZi = Substr(m.tcDate, 7, 2)
Endcase
lnAn = Int(Val(m.lcAn))
lnLuna = Int(Val(m.lcLuna))
lnZi = Int(Val(m.lcZi))
Try
ldData = Date(m.lnAn, m.lnLuna, m.lnZi)
Catch To loEx
ldData = {}
Endtry
Return m.ldData
Endfunc
*-- Formatare adresa in format semicolon pentru Oracle
Function FormatAddressForOracle
Parameters loBilling
Local lcAdresa, lcJudet, lcOras, lcStrada
lcJudet = Iif(Type('loBilling.region') = 'C', CleanWebText(loBilling.Region), "")
lcOras = Iif(Type('loBilling.city') = 'C', CleanWebText(loBilling.city), "")
lcStrada = Iif(Type('loBilling.address') = 'C', CleanWebText(loBilling.address), "")
lcAdresa = "JUD:" + lcJudet + ";" + lcOras + ";" + lcStrada
Return lcAdresa
Endfunc
*-- Construire observatii comanda
Function BuildOrderObservations
Parameters loOrder
Local lcObservatii
lcObservatii = ""
If Type('loOrder.payment') = 'O' And Type('loOrder.payment.name') = 'C'
lcObservatii = lcObservatii + "Payment: " + CleanWebText(loOrder.Payment.Name) + "; "
Endif
If Type('loOrder.delivery') = 'O' And Type('loOrder.delivery.name') = 'C'
lcObservatii = lcObservatii + "Delivery: " + CleanWebText(loOrder.delivery.Name) + "; "
Endif
If Type('loOrder.status') = 'C'
lcObservatii = lcObservatii + "Status: " + CleanWebText(loOrder.Status) + "; "
Endif
If Type('loOrder.source') = 'C'
lcObservatii = lcObservatii + "Source: " + CleanWebText(loOrder.Source)
If Type('loOrder.sales_channel') = 'C'
lcObservatii = lcObservatii + " " + CleanWebText(loOrder.sales_channel)
Endif
lcObservatii = lcObservatii + "; "
Endif
If Type('loOrder.shipping') = 'O' And Type('loOrder.billing') = 'O'
If Type('loOrder.shipping.address') = 'C' And Type('loOrder.billing.address') = 'C'
If !Alltrim(loOrder.shipping.address) == Alltrim(loOrder.billing.address)
lcObservatii = lcObservatii + "Shipping: " + CleanWebText(loOrder.shipping.address)
If Type('loOrder.shipping.city') = 'C'
lcObservatii = lcObservatii + ", " + CleanWebText(loOrder.shipping.city)
Endif
lcObservatii = lcObservatii + "; "
Endif
Endif
Endif
If Len(lcObservatii) > 500
lcObservatii = Left(lcObservatii, 497) + "..."
Endif
Return lcObservatii
Endfunc
*-- Obtinere detalii eroare Oracle (single-line, fara SQL)
Function GetOracleErrorDetails
Local lcError, laError[1], lnErrorLines, lnIndex
lcError = ""
lnErrorLines = Aerror(laError)
If lnErrorLines > 0
For lnIndex = 1 To lnErrorLines
If lnIndex > 1
lcError = lcError + " | "
Endif
lcError = lcError + Alltrim(Str(laError[lnIndex, 1])) + ": " + laError[lnIndex, 2]
Endfor
Endif
If Empty(lcError)
lcError = "Eroare Oracle nedefinita"
Endif
*-- Compact: inlocuieste newlines cu spatii
lcError = Strtran(lcError, CHR(13) + CHR(10), " ")
lcError = Strtran(lcError, CHR(10), " ")
lcError = Strtran(lcError, CHR(13), " ")
Return lcError
Endfunc
*-- Clasifica eroarea Oracle intr-un format compact
*-- Returneaza: "SKU_NOT_FOUND: sku" / "PRICE_POLICY: sku" / eroarea bruta
Function ClassifyImportError
Lparameters tcErrorDetails
Local lcText, lcSku, lnPos, lcSearch
lcText = Iif(Empty(tcErrorDetails), "", tcErrorDetails)
*-- SKU negasit
lcSearch = "NOM_ARTICOLE: "
lnPos = Atc(lcSearch, lcText)
If lnPos > 0
lcSku = Alltrim(Getwordnum(Substr(lcText, lnPos + Len(lcSearch)), 1))
Return "SKU_NOT_FOUND: " + lcSku
Endif
*-- Eroare adaugare articol (include pretul)
lcSearch = "Eroare adaugare articol "
lnPos = Atc(lcSearch, lcText)
If lnPos > 0
lcSku = Alltrim(Getwordnum(Substr(lcText, lnPos + Len(lcSearch)), 1))
Return "PRICE_POLICY: " + lcSku
Endif
*-- Eroare pret fara SKU (inainte de fix-ul Oracle)
If Atc("Pretul pentru acest articol", lcText) > 0
Return "PRICE_POLICY: (SKU necunoscut)"
Endif
*-- Eroare generica - primele 100 caractere
Return Left(lcText, 100)
Endfunc
*-- Colectare SKU-uri lipsa din mesajele de eroare Oracle
Function CollectFailedSKUs
Lparameters tcErrorDetails
Local lcSku, lnPos, lcSearch, lcText
If Empty(tcErrorDetails)
Return
Endif
lcText = tcErrorDetails
*-- Pattern 1: "SKU negasit in ARTICOLE_TERTI si NOM_ARTICOLE: XXXXX"
lcSearch = "NOM_ARTICOLE: "
lnPos = Atc(lcSearch, lcText)
If lnPos > 0
lcSku = Alltrim(Getwordnum(Substr(lcText, lnPos + Len(lcSearch)), 1))
If !Empty(lcSku)
AddUniqueSKU(lcSku)
Endif
Endif
*-- Pattern 2: "Eroare adaugare articol XXXXX (CODMAT:" sau "Eroare adaugare articol XXXXX:"
lcSearch = "Eroare adaugare articol "
lnPos = Atc(lcSearch, lcText)
If lnPos > 0
lcSku = Alltrim(Getwordnum(Substr(lcText, lnPos + Len(lcSearch)), 1))
If !Empty(lcSku)
AddUniqueSKU(lcSku)
Endif
Endif
Return
Endfunc
*-- Adauga un SKU in gcFailedSKUs daca nu exista deja
Function AddUniqueSKU
Lparameters tcSku
Local lcSku
lcSku = Alltrim(tcSku)
If Empty(lcSku)
Return
Endif
If Empty(gcFailedSKUs)
gcFailedSKUs = lcSku
Else
If !(CHR(10) + lcSku + CHR(10)) $ (CHR(10) + gcFailedSKUs + CHR(10))
gcFailedSKUs = gcFailedSKUs + CHR(10) + lcSku
Endif
Endif
Return
Endfunc
*-- Executie adapter configurat
Function ExecuteAdapter
Local llSuccess, lcAdapterPath
llSuccess = .F.
Try
lcAdapterPath = gcAppPath + goSettings.AdapterProgram
If File(lcAdapterPath)
Do (lcAdapterPath)
llSuccess = .T.
Else
LogMessage("EROARE: Adapter negasit: " + lcAdapterPath, "ERROR", gcLogFile)
Endif
Catch To loError
LogMessage("EROARE adapter: " + loError.Message, "ERROR", gcLogFile)
Endtry
Return llSuccess
Endfunc