*-- 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