*-- 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 PRIVATE goConnectie, goSettings, goAppSetup LOCAL lcJsonPattern, laJsonFiles[1], lnJsonFiles, lnIndex, lcJsonFile LOCAL loJsonData, lcJsonContent, lnOrderCount, lnOrderIndex LOCAL loOrder, lcResult, llProcessSuccess, lcPath goConnectie = NULL *-- 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 *-- Initializare logging gcLogFile = InitLog("sync_comenzi") LogMessage("=== SYNC COMENZI WEB > ORACLE ROA ===", "INFO", gcLogFile) *-- Creare si initializare clasa setup aplicatie goAppSetup = CREATEOBJECT("ApplicationSetup", gcAppPath) *-- Setup complet cu validare si afisare configuratie IF !goAppSetup.Initialize() LogMessage("EROARE: Setup-ul aplicatiei a esuat sau necesita configurare!", "ERROR", gcLogFile) RETURN .F. ENDIF *-- Obtinere setari din clasa goSettings = goAppSetup.GetSettings() *-- Verificare directoare necesare IF !DIRECTORY(gcAppPath + "output") LogMessage("EROARE: Directorul output/ nu exista! Ruleaza mai intai adapter-ul web", "ERROR", gcLogFile) RETURN .F. ENDIF *-- Rulare automata adapter pentru obtinere comenzi (daca este configurat) IF goSettings.AutoRunAdapter LogMessage("Rulez adapter pentru obtinere comenzi: " + goSettings.AdapterProgram, "INFO", gcLogFile) IF !ExecuteAdapter() LogMessage("EROARE la rularea adapter-ului, continuez cu fisierele JSON existente", "WARN", gcLogFile) ENDIF ELSE LogMessage("AutoRunAdapter este dezactivat, folosesc doar fisierele JSON existente", "INFO", gcLogFile) ENDIF SET STEP ON *-- Gasire fisiere JSON comenzi din pattern configurat lcJsonPattern = gcAppPath + "output\" + goSettings.JsonFilePattern lnJsonFiles = ADIR(laJsonFiles, lcJsonPattern) IF lnJsonFiles = 0 LogMessage("AVERTISMENT: Nu au fost gasite fisiere JSON cu comenzi web", "WARN", gcLogFile) LogMessage("Ruleaza mai intai adapter-ul web cu GetOrders=1 in settings.ini", "INFO", gcLogFile) RETURN .T. ENDIF LogMessage("Gasite " + TRANSFORM(lnJsonFiles) + " fisiere JSON cu comenzi web", "INFO", gcLogFile) *-- Incercare conectare Oracle (folosind conexiunea existenta din sistem) IF !ConnectToOracle() LogMessage("EROARE: Nu s-a putut conecta la Oracle ROA", "ERROR", gcLogFile) RETURN .F. ENDIF *-- Procesare fiecare fisier JSON gasit FOR lnIndex = 1 TO lnJsonFiles lcJsonFile = gcAppPath + "output\" + laJsonFiles[lnIndex, 1] LogMessage("Procesez fisierul: " + laJsonFiles[lnIndex, 1], "INFO", gcLogFile) *-- Citire si parsare JSON TRY lcJsonContent = FILETOSTR(lcJsonFile) IF EMPTY(lcJsonContent) LogMessage("AVERTISMENT: Fisier JSON gol - " + laJsonFiles[lnIndex, 1], "WARN", gcLogFile) LOOP ENDIF *-- Parsare JSON array cu comenzi loJsonData = nfJsonRead(lcJsonContent) IF ISNULL(loJsonData) LogMessage("EROARE: Nu s-a putut parsa JSON-ul din " + laJsonFiles[lnIndex, 1], "ERROR", gcLogFile) LOOP ENDIF *-- Verificare daca este array IF TYPE('loJsonData') != 'O' LogMessage("EROARE: JSON-ul nu este un array valid - " + laJsonFiles[lnIndex, 1], "ERROR", gcLogFile) LOOP ENDIF SET STEP ON *-- Obtinere numar comenzi din array lnOrderCount = 0 IF TYPE('loJsonData.array') = 'U' lnOrderCount = ALEN(loJsonData.array) ENDIF LogMessage("Gasite " + TRANSFORM(lnOrderCount) + " comenzi in " + laJsonFiles[lnIndex, 1], "INFO", gcLogFile) *-- Procesare fiecare comanda din JSON FOR EACH loOrder IN loJsonData.array IF TYPE('loOrder') = 'O' gnOrdersProcessed = gnOrdersProcessed + 1 llProcessSuccess = ProcessWebOrder(loOrder) IF llProcessSuccess gnOrdersSuccess = gnOrdersSuccess + 1 ELSE gnOrdersErrors = gnOrdersErrors + 1 ENDIF ENDIF ENDFOR CATCH TO loError LogMessage("EROARE la procesarea fisierului " + laJsonFiles[lnIndex, 1] + ": " + loError.Message, "ERROR", gcLogFile) gnOrdersErrors = gnOrdersErrors + 1 ENDTRY ENDFOR *-- Inchidere conexiune Oracle DisconnectFromOracle() *-- Logging final cu statistici LogMessage("=== PROCESARE COMPLETA ===", "INFO", gcLogFile) LogMessage("Total comenzi procesate: " + TRANSFORM(gnOrdersProcessed), "INFO", gcLogFile) LogMessage("Comenzi importate cu succes: " + TRANSFORM(gnOrdersSuccess), "INFO", gcLogFile) LogMessage("Comenzi cu erori: " + TRANSFORM(gnOrdersErrors), "INFO", gcLogFile) CloseLog(gnStartTime, 0, gnOrdersProcessed, gcLogFile) RETURN .T. *-- =================================================================== *-- HELPER FUNCTIONS *-- =================================================================== *-- Functie pentru conectarea la Oracle folosind setarile din settings.ini FUNCTION ConnectToOracle LOCAL llSuccess, lcConnectionString, lnHandle llSuccess = .F. TRY *-- Conectare Oracle folosind datele din settings.ini lnHandle = SQLCONNECT(goSettings.OracleDSN, goSettings.OracleUser, goSettings.OraclePassword) IF lnHandle > 0 goConnectie = lnHandle llSuccess = .T. LogMessage("Conectare Oracle reusita - Handle: " + TRANSFORM(lnHandle), "INFO", gcLogFile) LogMessage("DSN: " + goSettings.OracleDSN + " | User: " + goSettings.OracleUser, "DEBUG", gcLogFile) ELSE LogMessage("EROARE: Conectare Oracle esuata - Handle: " + TRANSFORM(lnHandle), "ERROR", gcLogFile) LogMessage("DSN: " + goSettings.OracleDSN + " | User: " + goSettings.OracleUser, "ERROR", gcLogFile) ENDIF CATCH TO loError LogMessage("EROARE la conectarea Oracle: " + loError.Message, "ERROR", gcLogFile) ENDTRY RETURN llSuccess ENDFUNC *-- Functie pentru deconectarea de la Oracle FUNCTION DisconnectFromOracle IF TYPE('goConnectie') = 'N' AND goConnectie > 0 SQLDISCONNECT(goConnectie) LogMessage("Deconectare Oracle reusita", "INFO", gcLogFile) ENDIF RETURN .T. ENDFUNC *-- Functie principala de procesare comanda web FUNCTION ProcessWebOrder PARAMETERS loOrder LOCAL llSuccess, lcOrderNumber, lcOrderDate, lnPartnerID, lcArticlesJSON LOCAL lcObservatii, lcSQL, lnResult, lcErrorDetails llSuccess = .F. TRY *-- Validare comanda IF !ValidateWebOrder(loOrder) LogMessage("EROARE: Comanda web invalida - lipsesc date obligatorii", "ERROR", gcLogFile) RETURN .F. ENDIF *-- Extragere date comanda lcOrderNumber = CleanWebText(TRANSFORM(loOrder.number)) lcOrderDate = ConvertWebDate(loOrder.date) LogMessage("Procesez comanda: " + lcOrderNumber + " din " + lcOrderDate, "INFO", gcLogFile) *-- Procesare partener (billing address) lnPartnerID = ProcessPartnerFromBilling(loOrder.billing) IF lnPartnerID <= 0 LogMessage("EROARE: Nu s-a putut procesa partenerul pentru comanda " + lcOrderNumber, "ERROR", gcLogFile) RETURN .F. ENDIF LogMessage("Partener identificat/creat: ID=" + TRANSFORM(lnPartnerID), "INFO", gcLogFile) *-- Construire JSON articole lcArticlesJSON = BuildArticlesJSON(loOrder.items) IF EMPTY(lcArticlesJSON) LogMessage("EROARE: Nu s-au gasit articole valide in comanda " + lcOrderNumber, "ERROR", gcLogFile) RETURN .F. ENDIF *-- Construire observatii cu detalii suplimentare lcObservatii = BuildOrderObservations(loOrder) *-- Apel package Oracle pentru import comanda lcSQL = "SELECT PACK_IMPORT_COMENZI.importa_comanda_web(?, TO_DATE(?, 'YYYY-MM-DD'), ?, ?, NULL, ?) AS ID_COMANDA FROM dual" lnResult = SQLEXEC(goConnectie, lcSQL, ; lcOrderNumber, ; && p_nr_comanda_ext lcOrderDate, ; && p_data_comanda lnPartnerID, ; && p_id_partener lcArticlesJSON, ; && p_json_articole lcObservatii, ; && p_observatii "cursor_comanda") IF lnResult > 0 AND RECCOUNT("cursor_comanda") > 0 AND cursor_comanda.ID_COMANDA > 0 LogMessage("SUCCES: Comanda importata - ID Oracle: " + TRANSFORM(cursor_comanda.ID_COMANDA), "INFO", gcLogFile) USE IN cursor_comanda llSuccess = .T. ELSE *-- Obtinere detalii eroare Oracle lcErrorDetails = GetOracleErrorDetails() LogMessage("EROARE: Import comanda esuat pentru " + lcOrderNumber + " - " + lcErrorDetails, "ERROR", gcLogFile) IF USED("cursor_comanda") USE IN cursor_comanda ENDIF ENDIF CATCH TO loError LogMessage("EXCEPTIE la procesarea comenzii " + lcOrderNumber + ": " + loError.Message, "ERROR", gcLogFile) ENDTRY RETURN llSuccess ENDFUNC *-- Functie pentru validarea comenzii web FUNCTION ValidateWebOrder PARAMETERS loOrder LOCAL llValid llValid = .T. *-- Verificari obligatorii 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 *-- Functie pentru procesarea partenerului din billing GoMag FUNCTION ProcessPartnerFromBilling PARAMETERS loBilling LOCAL lnPartnerID, lcDenumire, lcCodFiscal, lcAdresa, lcTelefon, lcEmail LOCAL lcSQL, lnResult lnPartnerID = 0 TRY *-- Extragere date partener din datele billing LOCAL lnIsPersoanaJuridica IF TYPE('loBilling.company') = 'O' AND !EMPTY(loBilling.company.name) *-- Companie - persoana juridica lcDenumire = CleanWebText(loBilling.company.name) lcCodFiscal = IIF(TYPE('loBilling.company.code') = 'C', loBilling.company.code, NULL) lnIsPersoanaJuridica = 1 && Persoana juridica ELSE *-- Persoana fizica lcDenumire = CleanWebText(ALLTRIM(loBilling.firstname) + " " + ALLTRIM(loBilling.lastname)) lcCodFiscal = NULL && Persoanele fizice nu au CUI in platformele web lnIsPersoanaJuridica = 0 && Persoana fizica ENDIF *-- Formatare adresa pentru Oracle (format semicolon cu prefix JUD:) lcAdresa = FormatAddressForOracle(loBilling) *-- Date contact lcTelefon = IIF(TYPE('loBilling.phone') = 'C', loBilling.phone, "") lcEmail = IIF(TYPE('loBilling.email') = 'C', loBilling.email, "") LogMessage("Partener: " + lcDenumire + " | CUI: " + IIF(ISNULL(lcCodFiscal), "NULL", lcCodFiscal) + " | Tip: " + IIF(lnIsPersoanaJuridica = 1, "JURIDICA", "FIZICA"), "DEBUG", gcLogFile) *-- Apel package Oracle IMPORT_PARTENERI (PROCEDURA cu parametru OUT) *-- Folosind sintaxa corecta pentru parametrii OUT in VFP LOCAL lnPartnerResult lnPartnerResult = 0 lcSQL = "BEGIN PACK_IMPORT_PARTENERI.cauta_sau_creeaza_partener(?lcCodFiscal, ?lcDenumire, ?lcAdresa, ?lcTelefon, ?lcEmail, ?lnIsPersoanaJuridica, ?@lnPartnerResult); END;" lnResult = SQLEXEC(goConnectie, lcSQL) IF lnResult > 0 lnPartnerID = lnPartnerResult LogMessage("Partener procesat cu succes: ID=" + TRANSFORM(lnPartnerID), "DEBUG", gcLogFile) ELSE *-- Obtinere detalii eroare Oracle lcErrorDetails = GetOracleErrorDetails() LogMessage("EROARE la apelul procedurii PACK_IMPORT_PARTENERI pentru: " + lcDenumire + " - " + lcErrorDetails, "ERROR", gcLogFile) lnPartnerID = 0 ENDIF CATCH TO loError LogMessage("EXCEPTIE la procesarea partenerului: " + loError.Message, "ERROR", gcLogFile) ENDTRY RETURN lnPartnerID ENDFUNC *-- Functie pentru construirea JSON-ului cu articole conform package Oracle FUNCTION BuildArticlesJSON PARAMETERS loItems LOCAL lcJSON, lnItemCount, lnIndex, lcItemProp, loItem LOCAL lcSku, lcQuantity, lcPrice lcJSON = "" TRY IF TYPE('loItems') != 'O' RETURN "" ENDIF lnItemCount = AMEMBERS(laItems, loItems, 0) IF lnItemCount = 0 RETURN "" ENDIF lcJSON = "[" FOR lnIndex = 1 TO lnItemCount lcItemProp = laItems[lnIndex] loItem = EVALUATE('loItems.' + lcItemProp) IF TYPE('loItem') = 'O' *-- Extragere date articol lcSku = IIF(TYPE('loItem.sku') = 'C', CleanWebText(loItem.sku), "") lcQuantity = IIF(TYPE('loItem.quantity') = 'C' OR TYPE('loItem.quantity') = 'N', TRANSFORM(VAL(TRANSFORM(loItem.quantity))), "1") lcPrice = IIF(TYPE('loItem.price') = 'C' OR TYPE('loItem.price') = 'N', TRANSFORM(VAL(TRANSFORM(loItem.price))), "0") IF !EMPTY(lcSku) *-- Adaugare virgula pentru elementele urmatoare IF lnIndex > 1 lcJSON = lcJSON + "," ENDIF *-- Format JSON conform package Oracle: {"sku":"...", "cantitate":..., "pret":...} lcJSON = lcJSON + '{"sku":"' + lcSku + '","cantitate":' + lcQuantity + ',"pret":' + lcPrice + '}' ENDIF ENDIF ENDFOR lcJSON = lcJSON + "]" CATCH TO loError LogMessage("EROARE la construirea JSON articole: " + loError.Message, "ERROR", gcLogFile) lcJSON = "" ENDTRY RETURN lcJSON ENDFUNC *-- Functie pentru curatarea textului web (HTML entities → ASCII simplu) FUNCTION CleanWebText PARAMETERS tcText LOCAL lcResult IF EMPTY(tcText) OR TYPE('tcText') != 'C' RETURN "" ENDIF lcResult = tcText *-- Conversie HTML entities in caractere simple (fara diacritice) lcResult = STRTRAN(lcResult, 'ă', 'a') && ă → a lcResult = STRTRAN(lcResult, 'ș', 's') && ș → s lcResult = STRTRAN(lcResult, 'ț', 't') && ț → t lcResult = STRTRAN(lcResult, 'î', 'i') && î → i lcResult = STRTRAN(lcResult, 'â', 'a') && â → a lcResult = STRTRAN(lcResult, '&', '&') lcResult = STRTRAN(lcResult, '<', '<') lcResult = STRTRAN(lcResult, '>', '>') lcResult = STRTRAN(lcResult, '"', '"') *-- Eliminare tag-uri HTML simple lcResult = STRTRAN(lcResult, '
', ' ') lcResult = STRTRAN(lcResult, '
', ' ') lcResult = STRTRAN(lcResult, '
', ' ') RETURN ALLTRIM(lcResult) ENDFUNC *-- Functie pentru conversia datei web in format Oracle FUNCTION ConvertWebDate PARAMETERS tcWebDate LOCAL lcResult IF EMPTY(tcWebDate) OR TYPE('tcWebDate') != 'C' RETURN DTOS(DATE()) ENDIF *-- Web date format: "2025-08-27 16:32:43" → "2025-08-27" lcResult = LEFT(tcWebDate, 10) *-- Validare format YYYY-MM-DD IF LEN(lcResult) = 10 AND SUBSTR(lcResult, 5, 1) = '-' AND SUBSTR(lcResult, 8, 1) = '-' RETURN lcResult ELSE RETURN DTOS(DATE()) ENDIF ENDFUNC *-- Functie pentru formatarea adresei in format semicolon pentru Oracle FUNCTION FormatAddressForOracle PARAMETERS loBilling LOCAL lcAdresa, lcJudet, lcOras, lcStrada *-- Extragere componente adresa lcJudet = IIF(TYPE('loBilling.region') = 'C', CleanWebText(loBilling.region), "Bucuresti") lcOras = IIF(TYPE('loBilling.city') = 'C', CleanWebText(loBilling.city), "BUCURESTI") lcStrada = IIF(TYPE('loBilling.address') = 'C', CleanWebText(loBilling.address), "Adresa necunoscuta") *-- Format semicolon cu prefix JUD: conform specificatiilor Oracle lcAdresa = "JUD:" + lcJudet + ";" + lcOras + ";" + lcStrada RETURN lcAdresa ENDFUNC *-- Functie pentru construirea observatiilor comenzii FUNCTION BuildOrderObservations PARAMETERS loOrder LOCAL lcObservatii lcObservatii = "" *-- Informatii plata si livrare 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 *-- Status si sursa 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 *-- Verificare adrese diferite shipping vs billing 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 *-- Limitare lungime observatii pentru Oracle IF LEN(lcObservatii) > 500 lcObservatii = LEFT(lcObservatii, 497) + "..." ENDIF RETURN lcObservatii ENDFUNC *-- Functie pentru obtinerea detaliilor erorii Oracle FUNCTION GetOracleErrorDetails LOCAL lcError, laError[1], lnErrorLines, lnIndex lcError = "" *-- Obtinere eroare Oracle 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 RETURN lcError ENDFUNC *-- Functie pentru executia adapter-ului configurat FUNCTION ExecuteAdapter LOCAL llSuccess, lcAdapterPath llSuccess = .F. TRY lcAdapterPath = gcAppPath + goSettings.AdapterProgram IF FILE(lcAdapterPath) LogMessage("Executie adapter: " + lcAdapterPath, "INFO", gcLogFile) DO (lcAdapterPath) llSuccess = .T. LogMessage("Adapter executat cu succes", "INFO", gcLogFile) ELSE LogMessage("EROARE: Adapter-ul nu a fost gasit la: " + lcAdapterPath, "ERROR", gcLogFile) ENDIF CATCH TO loError LogMessage("EXCEPTIE la executia adapter-ului " + goSettings.AdapterProgram + ": " + loError.Message, "ERROR", gcLogFile) ENDTRY RETURN llSuccess ENDFUNC *-- Orchestrator complet pentru sincronizarea comenzilor web cu Oracle ROA *-- Caracteristici: *-- - Citeste JSON-urile generate de gomag-vending.prg *-- - Proceseaza comenzile cu toate helper functions necesare *-- - Integreaza cu package-urile Oracle validate in Phase 1 *-- - Logging complet cu statistici de procesare *-- - Error handling pentru toate situatiile *-- - Support pentru toate formatele GoMag (billing/shipping, companii/persoane fizice)