Compare commits

...

2 Commits

Author SHA1 Message Date
fd48ca480b Fix orders counting bug and improve logging
- Fixed incorrect order count in logging (counted JSON properties instead of actual orders)
- Added proper array declaration for AMEMBERS() function
- Added comprehensive logging system with timestamps and levels
- Added silent operation mode (removed SET STEP ON)
- Added output directory creation
- Enhanced error handling with proper logging instead of file dumps
- Added statistics tracking for products and orders processed

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-27 16:26:21 +03:00
8324a26705 comenzi 2025-08-27 15:15:59 +03:00
7 changed files with 710 additions and 204 deletions

7
.gitignore vendored
View File

@@ -1,2 +1,9 @@
*.fxp *.fxp
*.FXP
*.bak *.bak
*.BAK
*.csv
*.json
*.err
*.ERR
*.log

View File

@@ -34,6 +34,19 @@ Main script that handles:
DO gomag-vending.prg DO gomag-vending.prg
``` ```
### Running from Windows Command Line
Use the provided batch file for easy execution:
```cmd
run-gomag.bat
```
Or directly with Visual FoxPro executable:
```cmd
"C:\Program Files (x86)\Microsoft Visual FoxPro 9\vfp9.exe" -T "path\to\gomag-vending-test.prg"
```
The batch file uses `%~dp0` to automatically detect the current directory, making it portable across different locations.
### Testing Connectivity ### Testing Connectivity
The script includes a `TestConnectivity()` function for internet connectivity testing. The script includes a `TestConnectivity()` function for internet connectivity testing.
@@ -54,7 +67,8 @@ The script includes a `TestConnectivity()` function for internet connectivity te
## File Structure ## File Structure
``` ```
/ /
├── gomag-vending.prg # Main application script ├── gomag-vending-test.prg # Main application script
├── run-gomag.bat # Windows batch file for easy execution
└── gomag_products_*.json # Generated API response files (timestamped) └── gomag_products_*.json # Generated API response files (timestamped)
``` ```

View File

@@ -2,6 +2,12 @@
*-- Autor: Claude AI *-- Autor: Claude AI
*-- Data: 26.08.2025 *-- Data: 26.08.2025
SET SAFETY OFF
SET EXACT ON
SET CENTURY ON
SET DELETED ON
SET DATE DMY
*-- Setari principale *-- Setari principale
LOCAL lcApiBaseUrl, lcApiUrl, lcApiKey, lcUserAgent, lcContentType LOCAL lcApiBaseUrl, lcApiUrl, lcApiKey, lcUserAgent, lcContentType
LOCAL loHttp, lcResponse, lcJsonResponse LOCAL loHttp, lcResponse, lcJsonResponse
@@ -9,7 +15,11 @@ LOCAL laHeaders[10], lnHeaderCount
Local lcApiShop, lcCsvFileName, lcErrorResponse, lcFileName, lcLogContent, lcLogFileName, lcPath Local lcApiShop, lcCsvFileName, lcErrorResponse, lcFileName, lcLogContent, lcLogFileName, lcPath
Local lcStatusText, lnStatusCode, loError Local lcStatusText, lnStatusCode, loError
Local lnLimit, lnCurrentPage, llHasMorePages, loAllJsonData, lnTotalPages, lnTotalProducts Local lnLimit, lnCurrentPage, llHasMorePages, loAllJsonData, lnTotalPages, lnTotalProducts
PRIVATE gcAppPath, loJsonData Local lcOrderApiUrl, loAllOrderData, lcOrderCsvFileName, lcOrderJsonFileName
Local ldStartDate, lcStartDateStr
Local lcIniFile, loSettings
LOCAL llGetProducts, llGetOrders
PRIVATE gcAppPath, loJsonData, gcLogFile, gnStartTime, gnProductsProcessed, gnOrdersProcessed
@@ -19,38 +29,97 @@ lcPath = gcAppPath + 'nfjson;'
SET PATH TO (m.lcPath) ADDITIVE SET PATH TO (m.lcPath) ADDITIVE
SET PROCEDURE TO nfjsonread.prg ADDITIVE SET PROCEDURE TO nfjsonread.prg ADDITIVE
SET PROCEDURE TO utils.prg ADDITIVE
SET PROCEDURE TO nfjsoncreate.prg ADDITIVE
*-- Configurare API - MODIFICA aceste valori conform documentatiei GoMag *-- Initializare logging si statistici
lcApiBaseUrl = "https://api.gomag.ro/api/v1/product/read/json?enabled=1" && URL de baza pentru lista de produse gnStartTime = SECONDS()
lcApiKey = "4c5e46df8f6c4f054fe2787de7a13d4a" && Cheia ta API de la GoMag gnProductsProcessed = 0
lcApiShop = "https://www.coffeepoint.ro" && URL-ul magazinului tau (ex: http://yourdomain.gomag.ro) gnOrdersProcessed = 0
lcUserAgent = "Mozilla/5.0" && User-Agent diferit de PostmanRuntime conform documentatiei gcLogFile = InitLog("gomag_sync")
lcContentType = "application/json"
lnLimit = 100 && Numarul maxim de produse per pagina (1-100) *-- Cream directorul output daca nu existe
LOCAL lcOutputDir
lcOutputDir = gcAppPath + "output"
IF !DIRECTORY(lcOutputDir)
MKDIR (lcOutputDir)
ENDIF
*-- Incarcarea setarilor din fisierul INI
lcIniFile = gcAppPath + "settings.ini"
*-- Verificare existenta fisier INI
IF !CheckIniFile(lcIniFile)
LogMessage("ATENTIE: Fisierul settings.ini nu a fost gasit!", "WARN", gcLogFile)
LogMessage("Cream un fisier settings.ini implicit...", "INFO", gcLogFile)
IF CreateDefaultIni(lcIniFile)
LogMessage("Fisier settings.ini creat cu succes.", "INFO", gcLogFile)
LogMessage("IMPORTANT: Modifica setarile din settings.ini (ApiKey, ApiShop) inainte de a rula scriptul din nou!", "INFO", gcLogFile)
RETURN .F.
ELSE
LogMessage("EROARE: Nu s-a putut crea fisierul settings.ini!", "ERROR", gcLogFile)
RETURN .F.
ENDIF
ENDIF
*-- Incarcarea setarilor
loSettings = LoadSettings(lcIniFile)
*-- Verificare setari obligatorii
IF EMPTY(loSettings.ApiKey) OR loSettings.ApiKey = "YOUR_API_KEY_HERE"
LogMessage("EROARE: ApiKey nu este setat in settings.ini!", "ERROR", gcLogFile)
RETURN .F.
ENDIF
IF EMPTY(loSettings.ApiShop) OR "yourstore.gomag.ro" $ loSettings.ApiShop
LogMessage("EROARE: ApiShop nu este setat corect in settings.ini!", "ERROR", gcLogFile)
RETURN .F.
ENDIF
*-- Configurare API din settings.ini
lcApiBaseUrl = loSettings.ApiBaseUrl
lcOrderApiUrl = loSettings.OrderApiUrl
lcApiKey = loSettings.ApiKey
lcApiShop = loSettings.ApiShop
lcUserAgent = loSettings.UserAgent
lcContentType = loSettings.ContentType
lnLimit = loSettings.Limit
llGetProducts = loSettings.GetProducts
llGetOrders = loSettings.GetOrders
lnCurrentPage = 1 && Pagina de start lnCurrentPage = 1 && Pagina de start
llHasMorePages = .T. && Flag pentru paginare llHasMorePages = .T. && Flag pentru paginare
loAllJsonData = NULL && Obiect pentru toate datele loAllJsonData = NULL && Obiect pentru toate datele
*-- Calculare data pentru ultimele X zile (din settings.ini)
ldStartDate = DATE() - loSettings.OrderDaysBack
lcStartDateStr = TRANSFORM(YEAR(ldStartDate)) + "-" + ;
RIGHT("0" + TRANSFORM(MONTH(ldStartDate)), 2) + "-" + ;
RIGHT("0" + TRANSFORM(DAY(ldStartDate)), 2)
*-- Verificare daca avem WinHttp disponibil *-- Verificare daca avem WinHttp disponibil
TRY TRY
loHttp = CREATEOBJECT("WinHttp.WinHttpRequest.5.1") loHttp = CREATEOBJECT("WinHttp.WinHttpRequest.5.1")
CATCH TO loError CATCH TO loError
? "Eroare la crearea obiectului WinHttp: " + loError.Message LogMessage("Eroare la crearea obiectului WinHttp: " + loError.Message, "ERROR", gcLogFile)
RETURN .F. RETURN .F.
ENDTRY ENDTRY
*-- Removed SET STEP ON for silent operation
*-- SECTIUNEA PRODUSE - se executa doar daca llGetProducts = .T.
IF llGetProducts
LogMessage("[PRODUCTS] Starting product retrieval", "INFO", gcLogFile)
*-- Bucla pentru preluarea tuturor produselor (paginare) *-- Bucla pentru preluarea tuturor produselor (paginare)
loAllJsonData = CREATEOBJECT("Empty") loAllJsonData = CREATEOBJECT("Empty")
ADDPROPERTY(loAllJsonData, "products", CREATEOBJECT("Empty")) ADDPROPERTY(loAllJsonData, "products", CREATEOBJECT("Empty"))
ADDPROPERTY(loAllJsonData, "total", 0) ADDPROPERTY(loAllJsonData, "total", 0)
ADDPROPERTY(loAllJsonData, "pages", 0) ADDPROPERTY(loAllJsonData, "pages", 0)
lnTotalProducts = 0 lnTotalProducts = 0
DO WHILE llHasMorePages DO WHILE llHasMorePages
*-- Construire URL cu paginare *-- Construire URL cu paginare
lcApiUrl = lcApiBaseUrl + "&page=" + TRANSFORM(lnCurrentPage) + "&limit=" + TRANSFORM(lnLimit) lcApiUrl = lcApiBaseUrl + "&page=" + TRANSFORM(lnCurrentPage) + "&limit=" + TRANSFORM(lnLimit)
? "Preluare pagina " + TRANSFORM(lnCurrentPage) + "..." LogMessage("[PRODUCTS] Page " + TRANSFORM(lnCurrentPage) + " fetching...", "INFO", gcLogFile)
*-- Configurare request *-- Configurare request
TRY TRY
@@ -91,8 +160,7 @@ DO WHILE llHasMorePages
IF TYPE('loJsonData.pages') = 'C' OR TYPE('loJsonData.pages') = 'N' IF TYPE('loJsonData.pages') = 'C' OR TYPE('loJsonData.pages') = 'N'
loAllJsonData.pages = VAL(TRANSFORM(loJsonData.pages)) loAllJsonData.pages = VAL(TRANSFORM(loJsonData.pages))
ENDIF ENDIF
? "Total produse: " + TRANSFORM(loAllJsonData.total) LogMessage("[PRODUCTS] Total items: " + TRANSFORM(loAllJsonData.total) + " | Pages: " + TRANSFORM(loAllJsonData.pages), "INFO", gcLogFile)
? "Total pagini: " + TRANSFORM(loAllJsonData.pages)
ENDIF ENDIF
*-- Adaugare produse din pagina curenta *-- Adaugare produse din pagina curenta
@@ -116,39 +184,31 @@ DO WHILE llHasMorePages
lnCurrentPage = lnCurrentPage + 1 lnCurrentPage = lnCurrentPage + 1
ELSE ELSE
*-- Salvare raspuns JSON raw in caz de eroare de parsare *-- Eroare parsare JSON
lcFileName = "gomag_error_page" + TRANSFORM(lnCurrentPage) + "_" + DTOS(DATE()) + "_" + STRTRAN(TIME(), ":", "") + ".json" LogMessage("[PRODUCTS] ERROR: JSON parsing failed for page " + TRANSFORM(lnCurrentPage), "ERROR", gcLogFile)
STRTOFILE(lcResponse, lcFileName)
llHasMorePages = .F. llHasMorePages = .F.
ENDIF ENDIF
ELSE ELSE
*-- Eroare HTTP - salvare in fisier de log *-- Eroare HTTP
lcLogFileName = "gomag_error_page" + TRANSFORM(lnCurrentPage) + "_" + DTOS(DATE()) + "_" + STRTRAN(TIME(), ":", "") + ".log" LogMessage("[PRODUCTS] HTTP Error " + TRANSFORM(lnStatusCode) + ": " + lcStatusText + " on page " + TRANSFORM(lnCurrentPage), "ERROR", gcLogFile)
lcLogContent = "HTTP Error " + TRANSFORM(lnStatusCode) + ": " + lcStatusText + CHR(13) + CHR(10)
*-- Incearca sa citesti raspunsul pentru detalii despre eroare *-- Detalii despre eroare daca sunt disponibile
TRY TRY
lcErrorResponse = loHttp.ResponseText lcErrorResponse = loHttp.ResponseText
IF !EMPTY(lcErrorResponse) IF !EMPTY(lcErrorResponse)
lcLogContent = lcLogContent + "Error Details:" + CHR(13) + CHR(10) + lcErrorResponse LogMessage("[PRODUCTS] Error details: " + LEFT(lcErrorResponse, 200), "ERROR", gcLogFile)
ENDIF ENDIF
CATCH CATCH
lcLogContent = lcLogContent + "Could not read error details" LogMessage("[PRODUCTS] Could not read error details", "WARN", gcLogFile)
ENDTRY ENDTRY
STRTOFILE(lcLogContent, lcLogFileName)
llHasMorePages = .F. llHasMorePages = .F.
ENDIF ENDIF
CATCH TO loError CATCH TO loError
*-- Salvare erori in fisier de log pentru pagina curenta *-- Script error in products section
lcLogFileName = "gomag_error_page" + TRANSFORM(lnCurrentPage) + "_" + DTOS(DATE()) + "_" + STRTRAN(TIME(), ":", "") + ".log" LogMessage("[PRODUCTS] Script Error #" + TRANSFORM(loError.ErrorNo) + ": " + loError.Message + " (Line: " + TRANSFORM(loError.LineNo) + ")", "ERROR", gcLogFile)
lcLogContent = "Script Error on page " + TRANSFORM(lnCurrentPage) + ":" + CHR(13) + CHR(10) +;
"Error Number: " + TRANSFORM(loError.ErrorNo) + CHR(13) + CHR(10) +;
"Error Message: " + loError.Message + CHR(13) + CHR(10) +;
"Error Line: " + TRANSFORM(loError.LineNo)
STRTOFILE(lcLogContent, lcLogFileName)
llHasMorePages = .F. llHasMorePages = .F.
ENDTRY ENDTRY
@@ -159,22 +219,280 @@ DO WHILE llHasMorePages
ENDDO ENDDO
*-- Creare fisier CSV cu toate produsele *-- Salvare array JSON cu toate produsele
IF !ISNULL(loAllJsonData) AND TYPE('loAllJsonData.products') = 'O' IF !ISNULL(loAllJsonData) AND TYPE('loAllJsonData.products') = 'O'
lcCsvFileName = "gomag_all_products_" + DTOS(DATE()) + "_" + STRTRAN(TIME(), ":", "") + ".csv" lcJsonFileName = lcOutputDir + "\gomag_all_products_" + DTOS(DATE()) + "_" + STRTRAN(TIME(), ":", "") + ".json"
DO CreateCsvFromJson WITH loAllJsonData, lcCsvFileName DO SaveProductsArray WITH loAllJsonData, lcJsonFileName
? "Fisier CSV creat: " + lcCsvFileName LogMessage("[PRODUCTS] JSON saved: " + lcJsonFileName, "INFO", gcLogFile)
*-- Calculam numarul de produse procesate
IF TYPE('loAllJsonData.products') = 'O'
lnPropCount = AMEMBERS(laProducts, loAllJsonData.products, 0)
gnProductsProcessed = lnPropCount
ENDIF
ENDIF
*-- Salvare si a datelor JSON complete ELSE
lcJsonFileName = "gomag_all_products_" + DTOS(DATE()) + "_" + STRTRAN(TIME(), ":", "") + ".json" LogMessage("[PRODUCTS] Skipped (disabled in settings)", "INFO", gcLogFile)
DO SaveCompleteJson WITH loAllJsonData, lcJsonFileName ENDIF
? "Fisier JSON complet creat: " + lcJsonFileName
*-- SECTIUNEA COMENZI - se executa doar daca llGetOrders = .T.
IF llGetOrders
LogMessage("[ORDERS] Starting orders retrieval (last " + TRANSFORM(loSettings.OrderDaysBack) + " days from " + lcStartDateStr + ")", "INFO", gcLogFile)
*-- Reinitializare pentru comenzi
lnCurrentPage = 1
llHasMorePages = .T.
loAllOrderData = CREATEOBJECT("Empty")
ADDPROPERTY(loAllOrderData, "orders", CREATEOBJECT("Empty"))
ADDPROPERTY(loAllOrderData, "total", 0)
ADDPROPERTY(loAllOrderData, "pages", 0)
*-- Bucla pentru preluarea comenzilor
DO WHILE llHasMorePages
*-- Construire URL cu paginare si filtrare pe data (folosind startDate conform documentatiei GoMag)
lcApiUrl = lcOrderApiUrl + "?startDate=" + lcStartDateStr + "&page=" + TRANSFORM(lnCurrentPage) + "&limit=" + TRANSFORM(lnLimit)
LogMessage("[ORDERS] Page " + TRANSFORM(lnCurrentPage) + " fetching...", "INFO", gcLogFile)
*-- Configurare request
TRY
*-- Initializare request GET
loHttp.Open("GET", lcApiUrl, .F.)
*-- Setare headers conform documentatiei GoMag
loHttp.SetRequestHeader("User-Agent", lcUserAgent)
loHttp.SetRequestHeader("Content-Type", lcContentType)
loHttp.SetRequestHeader("Accept", "application/json")
loHttp.SetRequestHeader("Apikey", lcApiKey) && Header pentru API Key
loHttp.SetRequestHeader("ApiShop", lcApiShop) && Header pentru shop URL
*-- Setari timeout
loHttp.SetTimeouts(30000, 30000, 30000, 30000) && 30 secunde pentru fiecare
*-- Trimitere request
loHttp.Send()
*-- Verificare status code
lnStatusCode = loHttp.Status
lcStatusText = loHttp.StatusText
IF lnStatusCode = 200
*-- Success - preluare raspuns
lcResponse = loHttp.ResponseText
*-- Parsare JSON cu nfjson
SET PATH TO nfjson ADDITIVE
loJsonData = nfJsonRead(lcResponse)
IF !ISNULL(loJsonData)
*-- Debug: Afisam structura JSON pentru prima pagina
IF lnCurrentPage = 1
LogMessage("[ORDERS] DEBUG: Analyzing JSON structure...", "DEBUG", gcLogFile)
lnPropCount = AMEMBERS(laJsonProps, loJsonData, 0)
FOR lnDebugIndex = 1 TO MIN(lnPropCount, 10) && Primele 10 proprietati
lcPropName = laJsonProps(lnDebugIndex)
lcPropType = TYPE('loJsonData.' + lcPropName)
LogMessage("[ORDERS] Property: " + lcPropName + " (Type: " + lcPropType + ")", "DEBUG", gcLogFile)
ENDFOR
ENDIF
*-- Prima pagina - setam informatiile generale
IF lnCurrentPage = 1
IF TYPE('loJsonData.total') = 'C' OR TYPE('loJsonData.total') = 'N'
loAllOrderData.total = VAL(TRANSFORM(loJsonData.total))
ENDIF
IF TYPE('loJsonData.pages') = 'C' OR TYPE('loJsonData.pages') = 'N'
loAllOrderData.pages = VAL(TRANSFORM(loJsonData.pages))
ENDIF
LogMessage("[ORDERS] Total items: " + TRANSFORM(loAllOrderData.total) + " | Pages: " + TRANSFORM(loAllOrderData.pages), "INFO", gcLogFile)
ENDIF
*-- Adaugare comenzi din pagina curenta
*-- API-ul GoMag returneaza un array direct de comenzi
LOCAL llHasOrders, lnOrdersFound
llHasOrders = .F.
lnOrdersFound = 0
*-- Verificam daca JSON-ul contine proprietatea orders
IF TYPE('loJsonData.orders') = 'O'
*-- Numaram comenzile din obiectul orders
LOCAL ARRAY laOrdersProps[1]
LOCAL lnOrdersCount
lnOrdersCount = AMEMBERS(laOrdersProps, loJsonData.orders, 0)
IF lnOrdersCount > 0
DO MergeOrdersArray WITH loAllOrderData, loJsonData
llHasOrders = .T.
lnOrdersFound = lnOrdersCount
LogMessage("[ORDERS] Found " + TRANSFORM(lnOrdersCount) + " orders in page " + TRANSFORM(lnCurrentPage), "INFO", gcLogFile)
gnOrdersProcessed = gnOrdersProcessed + lnOrdersCount
ENDIF
ELSE
*-- JSON-ul este direct un array de comenzi (backup logic)
lnDirectProps = AMEMBERS(laDirectProps, loJsonData, 0)
IF lnDirectProps > 0
*-- Cream un obiect temporar cu structura asteptata
LOCAL loTempData
loTempData = CREATEOBJECT("Empty")
ADDPROPERTY(loTempData, "orders", loJsonData)
DO MergeOrdersArray WITH loAllOrderData, loTempData
llHasOrders = .T.
lnOrdersFound = lnDirectProps
LogMessage("[ORDERS] Found " + TRANSFORM(lnDirectProps) + " orders in page " + TRANSFORM(lnCurrentPage), "INFO", gcLogFile)
gnOrdersProcessed = gnOrdersProcessed + lnDirectProps
ENDIF
ENDIF
IF !llHasOrders
LogMessage("[ORDERS] WARNING: No orders found in JSON response for page " + TRANSFORM(lnCurrentPage), "WARN", gcLogFile)
ENDIF
*-- Verificare daca mai sunt pagini
IF TYPE('loJsonData.pages') = 'C' OR TYPE('loJsonData.pages') = 'N'
lnTotalPages = VAL(TRANSFORM(loJsonData.pages))
IF lnCurrentPage >= lnTotalPages
llHasMorePages = .F.
ENDIF
ELSE
*-- Daca nu avem info despre pagini, verificam daca sunt comenzi
IF !llHasOrders
llHasMorePages = .F.
ENDIF
ENDIF
lnCurrentPage = lnCurrentPage + 1
ELSE
*-- Eroare parsare JSON pentru comenzi
LogMessage("[ORDERS] ERROR: JSON parsing failed for page " + TRANSFORM(lnCurrentPage), "ERROR", gcLogFile)
llHasMorePages = .F.
ENDIF
ELSE
*-- Eroare HTTP pentru comenzi
LogMessage("[ORDERS] HTTP Error " + TRANSFORM(lnStatusCode) + ": " + lcStatusText + " on page " + TRANSFORM(lnCurrentPage), "ERROR", gcLogFile)
*-- Detalii despre eroare daca sunt disponibile
TRY
lcErrorResponse = loHttp.ResponseText
IF !EMPTY(lcErrorResponse)
LogMessage("[ORDERS] Error details: " + LEFT(lcErrorResponse, 200), "ERROR", gcLogFile)
ENDIF
CATCH
LogMessage("[ORDERS] Could not read error details", "WARN", gcLogFile)
ENDTRY
llHasMorePages = .F.
ENDIF
CATCH TO loError
*-- Script error in orders section
LogMessage("[ORDERS] Script Error #" + TRANSFORM(loError.ErrorNo) + ": " + loError.Message + " (Line: " + TRANSFORM(loError.LineNo) + ")", "ERROR", gcLogFile)
llHasMorePages = .F.
ENDTRY
*-- Pauza scurta intre cereri pentru a evita rate limiting
IF llHasMorePages
INKEY(1) && Pauza de 1 secunda
ENDIF
ENDDO
*-- Salvare array JSON cu toate comenzile
IF !ISNULL(loAllOrderData) AND TYPE('loAllOrderData.orders') = 'O'
lcOrderJsonFileName = lcOutputDir + "\gomag_orders_last7days_" + DTOS(DATE()) + "_" + STRTRAN(TIME(), ":", "") + ".json"
DO SaveOrdersArray WITH loAllOrderData, lcOrderJsonFileName
LogMessage("[ORDERS] JSON saved: " + lcOrderJsonFileName, "INFO", gcLogFile)
ENDIF
ELSE
LogMessage("[ORDERS] Skipped (disabled in settings)", "INFO", gcLogFile)
ENDIF ENDIF
*-- Curatare *-- Curatare
loHttp = NULL loHttp = NULL
*-- Functie pentru unirea produselor din toate paginile *-- Inchidere logging cu statistici finale
CloseLog(gnStartTime, gnProductsProcessed, gnOrdersProcessed, gcLogFile)
*-- Functie pentru salvarea array-ului de produse in format JSON
PROCEDURE SaveProductsArray
PARAMETERS tloAllData, tcFileName
LOCAL lcJsonContent, lnPropCount, lnIndex, lcPropName, loProduct
*-- Incepe array-ul JSON
lcJsonContent = "[" + CHR(13) + CHR(10)
*-- Verifica daca avem produse
IF TYPE('tloAllData.products') = 'O'
lnPropCount = AMEMBERS(laProducts, tloAllData.products, 0)
FOR lnIndex = 1 TO lnPropCount
lcPropName = laProducts(lnIndex)
loProduct = EVALUATE('tloAllData.products.' + lcPropName)
IF TYPE('loProduct') = 'O'
*-- Adauga virgula pentru elementele anterioare
IF lnIndex > 1
lcJsonContent = lcJsonContent + "," + CHR(13) + CHR(10)
ENDIF
*-- Serializeaza produsul cu nfjsoncreate
lcProductJson = nfJsonCreate(loProduct, .F.)
lcJsonContent = lcJsonContent + " " + lcProductJson
ENDIF
ENDFOR
ENDIF
*-- Inchide array-ul JSON
lcJsonContent = lcJsonContent + CHR(13) + CHR(10) + "]"
*-- Salveaza fisierul
STRTOFILE(lcJsonContent, tcFileName)
ENDPROC
*-- Functie pentru salvarea array-ului de comenzi in format JSON
PROCEDURE SaveOrdersArray
PARAMETERS tloAllData, tcFileName
LOCAL lcJsonContent, lnPropCount, lnIndex, lcPropName, loOrder
*-- Incepe array-ul JSON
lcJsonContent = "[" + CHR(13) + CHR(10)
*-- Verifica daca avem comenzi
IF TYPE('tloAllData.orders') = 'O'
lnPropCount = AMEMBERS(laOrders, tloAllData.orders, 0)
FOR lnIndex = 1 TO lnPropCount
lcPropName = laOrders(lnIndex)
loOrder = EVALUATE('tloAllData.orders.' + lcPropName)
IF TYPE('loOrder') = 'O'
*-- Adauga virgula pentru elementele anterioare
IF lnIndex > 1
lcJsonContent = lcJsonContent + "," + CHR(13) + CHR(10)
ENDIF
*-- Serializeaza comanda cu nfjsoncreate
lcOrderJson = nfJsonCreate(loOrder, .F.)
lcJsonContent = lcJsonContent + " " + lcOrderJson
ENDIF
ENDFOR
ENDIF
*-- Inchide array-ul JSON
lcJsonContent = lcJsonContent + CHR(13) + CHR(10) + "]"
*-- Salveaza fisierul
STRTOFILE(lcJsonContent, tcFileName)
ENDPROC
*-- Functie pentru unirea produselor din toate paginile (versiune simpla)
PROCEDURE MergeProducts PROCEDURE MergeProducts
PARAMETERS tloAllData, tloPageData PARAMETERS tloAllData, tloPageData
@@ -198,176 +516,58 @@ ENDIF
ENDPROC ENDPROC
*-- Functie pentru salvarea datelor JSON complete *-- Functie pentru unirea comenzilor din array direct (structura GoMag)
PROCEDURE SaveCompleteJson PROCEDURE MergeOrdersArray
PARAMETERS tloJsonData, tcFileName PARAMETERS tloAllData, tloPageData
LOCAL lcJsonContent LOCAL lnPropCount, lnIndex, lcPropName, loOrder
*-- Construieste JSON simplu pentru salvare *-- Verifica daca avem comenzi in pagina curenta
lcJsonContent = '{' + CHR(13) + CHR(10) IF TYPE('tloPageData.orders') = 'O'
lcJsonContent = lcJsonContent + ' "total": ' + TRANSFORM(tloJsonData.total) + ',' + CHR(13) + CHR(10) *-- Itereaza prin toate comenzile din pagina (array direct)
lcJsonContent = lcJsonContent + ' "pages": ' + TRANSFORM(tloJsonData.pages) + ',' + CHR(13) + CHR(10) lnPropCount = AMEMBERS(laPageOrders, tloPageData.orders, 0)
lcJsonContent = lcJsonContent + ' "products": {' + CHR(13) + CHR(10)
*-- Adauga produsele (versiune simplificata)
LOCAL lnPropCount, lnIndex, lcPropName, loProduct
lnPropCount = AMEMBERS(laProducts, tloJsonData.products, 0)
FOR lnIndex = 1 TO lnPropCount
lcPropName = laProducts(lnIndex)
loProduct = EVALUATE('tloJsonData.products.' + lcPropName)
IF TYPE('loProduct') = 'O'
lcJsonContent = lcJsonContent + ' "' + lcPropName + '": {'
IF TYPE('loProduct.id') = 'C'
lcJsonContent = lcJsonContent + '"id": "' + loProduct.id + '",'
ENDIF
IF TYPE('loProduct.sku') = 'C'
lcJsonContent = lcJsonContent + '"sku": "' + loProduct.sku + '",'
ENDIF
IF TYPE('loProduct.name') = 'C'
lcJsonContent = lcJsonContent + '"name": "' + STRTRAN(loProduct.name, '"', '\"') + '",'
ENDIF
*-- Elimina ultima virgula
IF RIGHT(lcJsonContent, 1) = ','
lcJsonContent = LEFT(lcJsonContent, LEN(lcJsonContent) - 1)
ENDIF
lcJsonContent = lcJsonContent + '}'
IF lnIndex < lnPropCount
lcJsonContent = lcJsonContent + ','
ENDIF
lcJsonContent = lcJsonContent + CHR(13) + CHR(10)
ENDIF
ENDFOR
lcJsonContent = lcJsonContent + ' }' + CHR(13) + CHR(10)
lcJsonContent = lcJsonContent + '}' + CHR(13) + CHR(10)
STRTOFILE(lcJsonContent, tcFileName)
ENDPROC
*-- Functie pentru crearea fisierului CSV din datele JSON
PROCEDURE CreateCsvFromJson
PARAMETERS tloJsonData, tcCsvFileName
LOCAL lcCsvContent, lcCsvHeader, lcCsvRow
LOCAL lnProductCount, lnIndex
LOCAL loProduct
lcCsvContent = ""
lcCsvHeader = "ID,SKU,Name,Brand,Weight,Stock,Base_Price,Price,VAT_Included,Enabled,VAT,Currency,Ecotax" + CHR(13) + CHR(10)
lcCsvContent = lcCsvHeader
*-- Verifica daca avem produse in raspuns
IF TYPE('tloJsonData.products') = 'O'
*-- Itereaza prin toate produsele
lnPropCount = AMEMBERS(laProducts, tloJsonData.products, 0)
? "Procesare " + TRANSFORM(lnPropCount) + " produse pentru CSV..."
FOR lnIndex = 1 TO lnPropCount FOR lnIndex = 1 TO lnPropCount
lcPropName = laProducts(lnIndex) lcPropName = laPageOrders(lnIndex)
loProduct = EVALUATE('tloJsonData.products.' + lcPropName) loOrder = EVALUATE('tloPageData.orders.' + lcPropName)
IF TYPE('loProduct') = 'O' IF TYPE('loOrder') = 'O'
*-- Extrage datele produsului *-- Folosim ID-ul comenzii ca nume proprietate, sau un index secvential
lcCsvRow = ; LOCAL lcOrderId
IIF(TYPE('loProduct.id')='C', STRTRAN(loProduct.id, ',', ';'), '') + ',' +; lcOrderId = ""
IIF(TYPE('loProduct.sku')='C', STRTRAN(loProduct.sku, ',', ';'), '') + ',' +; IF TYPE('loOrder.id') = 'C'
IIF(TYPE('loProduct.name')='C', '"' + STRTRAN(STRTRAN(loProduct.name, '"', '""'), ',', ';') + '"', '') + ',' +; lcOrderId = "order_" + loOrder.id
IIF(TYPE('loProduct.brand')='C', STRTRAN(loProduct.brand, ',', ';'), '') + ',' +; ELSE
IIF(TYPE('loProduct.weight')='C', loProduct.weight, IIF(TYPE('loProduct.weight')='N', TRANSFORM(loProduct.weight), '')) + ',' +; lcOrderId = "order_" + TRANSFORM(lnIndex)
IIF(TYPE('loProduct.stock')='C', loProduct.stock, IIF(TYPE('loProduct.stock')='N', TRANSFORM(loProduct.stock), '')) + ',' +; ENDIF
IIF(TYPE('loProduct.base_price')='C', loProduct.base_price, IIF(TYPE('loProduct.base_price')='N', TRANSFORM(loProduct.base_price), '')) + ',' +;
IIF(TYPE('loProduct.price')='C', loProduct.price, IIF(TYPE('loProduct.price')='N', TRANSFORM(loProduct.price), '')) + ',' +;
IIF(TYPE('loProduct.vat_included')='C', loProduct.vat_included, IIF(TYPE('loProduct.vat_included')='N', TRANSFORM(loProduct.vat_included), '')) + ',' +;
IIF(TYPE('loProduct.enabled')='C', loProduct.enabled, IIF(TYPE('loProduct.enabled')='N', TRANSFORM(loProduct.enabled), '')) + ',' +;
IIF(TYPE('loProduct.vat')='C', loProduct.vat, IIF(TYPE('loProduct.vat')='N', TRANSFORM(loProduct.vat), '')) + ',' +;
IIF(TYPE('loProduct.currency')='C', loProduct.currency, '') + ',' +;
IIF(TYPE('loProduct.ecotax')='C', loProduct.ecotax, IIF(TYPE('loProduct.ecotax')='N', TRANSFORM(loProduct.ecotax), '')) +;
CHR(13) + CHR(10)
lcCsvContent = lcCsvContent + lcCsvRow *-- Adauga comanda la colectia principala
ADDPROPERTY(tloAllData.orders, lcOrderId, loOrder)
ENDIF ENDIF
ENDFOR ENDFOR
ENDIF ENDIF
*-- Salvare fisier CSV
STRTOFILE(lcCsvContent, tcCsvFileName)
? "CSV salvat cu " + TRANSFORM(lnPropCount) + " produse"
ENDPROC ENDPROC
*-- Functii helper pentru testare (optionale) *-- Functiile utilitare au fost mutate in utils.prg
*-- Test conectivitate internet *-- Scriptul cu paginare completa pentru preluarea tuturor produselor si comenzilor
FUNCTION TestConnectivity
LOCAL loHttp, llResult
llResult = .T.
TRY
loHttp = CREATEOBJECT("WinHttp.WinHttpRequest.5.1")
loHttp.Open("GET", "https://www.google.com", .F.)
loHttp.SetTimeouts(5000, 5000, 5000, 5000)
loHttp.Send()
IF loHttp.Status != 200
llResult = .F.
ENDIF
CATCH
llResult = .F.
ENDTRY
loHttp = NULL
RETURN llResult
ENDFUNC
*-- Functie pentru codificare URL
FUNCTION UrlEncode
PARAMETERS tcString
LOCAL lcResult, lcChar, lnI
lcResult = ""
FOR lnI = 1 TO LEN(tcString)
lcChar = SUBSTR(tcString, lnI, 1)
DO CASE
CASE ISALPHA(lcChar) OR ISDIGIT(lcChar) OR INLIST(lcChar, "-", "_", ".", "~")
lcResult = lcResult + lcChar
OTHERWISE
lcResult = lcResult + "%" + RIGHT("0" + TRANSFORM(ASC(lcChar), "@0"), 2)
ENDCASE
ENDFOR
RETURN lcResult
ENDFUNC
*-- Scriptul cu paginare completa pentru preluarea tuturor produselor
*-- Caracteristici principale: *-- Caracteristici principale:
*-- - Paginare automata pentru toate produsele (100 per pagina) *-- - Paginare automata pentru toate produsele si comenzile
*-- - Pauze intre cereri pentru respectarea rate limiting *-- - Pauze intre cereri pentru respectarea rate limiting
*-- - Creare fisier CSV cu toate produsele *-- - Salvare JSON array-uri pure (fara metadata de paginare)
*-- - Salvare fisier JSON complet cu toate datele *-- - Utilizare nfjsoncreate pentru generare JSON corecta
*-- - Logging separat pentru fiecare pagina in caz de eroare *-- - Logging separat pentru fiecare pagina in caz de eroare
*-- - Afisare progres in timpul executiei *-- - Afisare progres in timpul executiei
*-- INSTRUCTIUNI DE UTILIZARE: *-- INSTRUCTIUNI DE UTILIZARE:
*-- 1. Modifica lcApiKey cu cheia ta API de la GoMag *-- 1. Modifica settings.ini cu setarile tale:
*-- 2. Modifica lcApiShop cu URL-ul magazinului tau *-- - ApiKey: cheia ta API de la GoMag
*-- 3. Ruleaza scriptul - va prelua automat toate produsele *-- - ApiShop: URL-ul magazinului tau
*-- 4. Verifica fisierele generate: CSV si JSON cu toate produsele *-- - GetProducts: 1 pentru a prelua produse, 0 pentru a sari peste
*-- - GetOrders: 1 pentru a prelua comenzi, 0 pentru a sari peste
*-- - OrderDaysBack: numarul de zile pentru preluarea comenzilor
*-- 2. Ruleaza scriptul - va prelua doar ce ai selectat
*-- 3. Verifica fisierele JSON generate cu array-uri pure
*-- Script completat cu paginare - verificati fisierele generate *-- Script optimizat cu salvare JSON array-uri - verificati fisierele generate

Binary file not shown.

4
run-gomag.bat Normal file
View File

@@ -0,0 +1,4 @@
@echo off
cd /d "%~dp0"
"C:\Program Files (x86)\Microsoft Visual FoxPro 9\vfp9.exe" -T "%~dp0gomag-vending-test.prg"
pause

17
settings.ini Normal file
View File

@@ -0,0 +1,17 @@
[API]
ApiBaseUrl=https://api.gomag.ro/api/v1/product/read/json?enabled=1
OrderApiUrl=https://api.gomag.ro/api/v1/order/read/json
ApiKey=4c5e46df8f6c4f054fe2787de7a13d4a
ApiShop=https://www.coffeepoint.ro
UserAgent=Mozilla/5.0
ContentType=application/json
[PAGINATION]
Limit=100
[OPTIONS]
GetProducts=1
GetOrders=1
[FILTERS]
OrderDaysBack=7

264
utils.prg Normal file
View File

@@ -0,0 +1,264 @@
*-- utils.prg - Utilitare pentru GoMag API
*-- Functii pentru citirea/scrierea fisierelor INI si alte utilitare
*-- Autor: Claude AI
*-- Data: 27.08.2025
*-- Functie pentru citirea fisierelor INI private
*-- Returneaza valoarea din sectiunea si intrarea specificata sau blank daca nu e gasita
FUNCTION ReadPini
PARAMETERS cSection, cEntry, cINIFile
LOCAL cDefault, cRetVal, nRetLen
cDefault = ""
cRetVal = SPACE(255)
nRetLen = LEN(cRetVal)
DECLARE INTEGER GetPrivateProfileString IN WIN32API ;
STRING cSection, ;
STRING cEntry, ;
STRING cDefault, ;
STRING @cRetVal, ;
INTEGER nRetLen, ;
STRING cINIFile
nRetLen = GetPrivateProfileString(cSection, ;
cEntry, ;
cDefault, ;
@cRetVal, ;
nRetLen, ;
cINIFile)
RETURN LEFT(cRetVal, nRetLen)
ENDFUNC
*-- Functie pentru scrierea in fisierele INI private
*-- Returneaza .T. daca e successful, .F. daca nu
FUNCTION WritePini
PARAMETERS cSection, cEntry, cValue, cINIFile
LOCAL nRetVal
DECLARE INTEGER WritePrivateProfileString IN WIN32API ;
STRING cSection, ;
STRING cEntry, ;
STRING cValue, ;
STRING cINIFile
nRetVal = WritePrivateProfileString(cSection, ;
cEntry, ;
cValue, ;
cINIFile)
RETURN nRetVal = 1
ENDFUNC
*-- Functie pentru incarcarea tuturor setarilor din fisierul INI
FUNCTION LoadSettings
PARAMETERS cINIFile
LOCAL loSettings
*-- Cream un obiect pentru toate setarile
loSettings = CREATEOBJECT("Empty")
*-- Sectiunea API
ADDPROPERTY(loSettings, "ApiBaseUrl", ReadPini("API", "ApiBaseUrl", cINIFile))
ADDPROPERTY(loSettings, "OrderApiUrl", ReadPini("API", "OrderApiUrl", cINIFile))
ADDPROPERTY(loSettings, "ApiKey", ReadPini("API", "ApiKey", cINIFile))
ADDPROPERTY(loSettings, "ApiShop", ReadPini("API", "ApiShop", cINIFile))
ADDPROPERTY(loSettings, "UserAgent", ReadPini("API", "UserAgent", cINIFile))
ADDPROPERTY(loSettings, "ContentType", ReadPini("API", "ContentType", cINIFile))
*-- Sectiunea PAGINATION
ADDPROPERTY(loSettings, "Limit", VAL(ReadPini("PAGINATION", "Limit", cINIFile)))
*-- Sectiunea OPTIONS
ADDPROPERTY(loSettings, "GetProducts", ReadPini("OPTIONS", "GetProducts", cINIFile) = "1")
ADDPROPERTY(loSettings, "GetOrders", ReadPini("OPTIONS", "GetOrders", cINIFile) = "1")
*-- Sectiunea FILTERS
ADDPROPERTY(loSettings, "OrderDaysBack", VAL(ReadPini("FILTERS", "OrderDaysBack", cINIFile)))
RETURN loSettings
ENDFUNC
*-- Test conectivitate internet
FUNCTION TestConnectivity
LOCAL loHttp, llResult
llResult = .T.
TRY
loHttp = CREATEOBJECT("WinHttp.WinHttpRequest.5.1")
loHttp.Open("GET", "https://www.google.com", .F.)
loHttp.SetTimeouts(5000, 5000, 5000, 5000)
loHttp.Send()
IF loHttp.Status != 200
llResult = .F.
ENDIF
CATCH
llResult = .F.
ENDTRY
loHttp = NULL
RETURN llResult
ENDFUNC
*-- Functie pentru codificare URL
FUNCTION UrlEncode
PARAMETERS tcString
LOCAL lcResult, lcChar, lnI
lcResult = ""
FOR lnI = 1 TO LEN(tcString)
lcChar = SUBSTR(tcString, lnI, 1)
DO CASE
CASE ISALPHA(lcChar) OR ISDIGIT(lcChar) OR INLIST(lcChar, "-", "_", ".", "~")
lcResult = lcResult + lcChar
OTHERWISE
lcResult = lcResult + "%" + RIGHT("0" + TRANSFORM(ASC(lcChar), "@0"), 2)
ENDCASE
ENDFOR
RETURN lcResult
ENDFUNC
*-- Functie pentru verificarea existentei fisierului INI
FUNCTION CheckIniFile
PARAMETERS cINIFile
LOCAL llExists
TRY
llExists = FILE(cINIFile)
CATCH
llExists = .F.
ENDTRY
RETURN llExists
ENDFUNC
*-- Functie pentru crearea unui fisier INI implicit cu setari de baza
FUNCTION CreateDefaultIni
PARAMETERS cINIFile
LOCAL llSuccess
llSuccess = .T.
TRY
*-- Sectiunea API
WritePini("API", "ApiBaseUrl", "https://api.gomag.ro/api/v1/product/read/json?enabled=1", cINIFile)
WritePini("API", "OrderApiUrl", "https://api.gomag.ro/api/v1/order/read/json", cINIFile)
WritePini("API", "ApiKey", "YOUR_API_KEY_HERE", cINIFile)
WritePini("API", "ApiShop", "https://yourstore.gomag.ro", cINIFile)
WritePini("API", "UserAgent", "Mozilla/5.0", cINIFile)
WritePini("API", "ContentType", "application/json", cINIFile)
*-- Sectiunea PAGINATION
WritePini("PAGINATION", "Limit", "100", cINIFile)
*-- Sectiunea OPTIONS
WritePini("OPTIONS", "GetProducts", "1", cINIFile)
WritePini("OPTIONS", "GetOrders", "1", cINIFile)
*-- Sectiunea FILTERS
WritePini("FILTERS", "OrderDaysBack", "7", cINIFile)
CATCH
llSuccess = .F.
ENDTRY
RETURN llSuccess
ENDFUNC
*-- Functie pentru initializarea logging-ului
FUNCTION InitLog
PARAMETERS cBaseName
LOCAL lcLogFile, lcStartTime, lcLogHeader, lcLogDir
*-- Cream directorul log daca nu existe
lcLogDir = gcAppPath + "log"
IF !DIRECTORY(lcLogDir)
MKDIR (lcLogDir)
ENDIF
*-- Generam numele fisierului log cu timestamp in directorul log
lcStartTime = DTOS(DATE()) + "_" + STRTRAN(TIME(), ":", "")
lcLogFile = lcLogDir + "\" + cBaseName + "_" + lcStartTime + ".log"
*-- Header pentru log
lcLogHeader = "[" + TIME() + "] [START] === GoMag Sync Started ===" + CHR(13) + CHR(10)
lcLogHeader = lcLogHeader + "[" + TIME() + "] [INFO ] Date: " + DTOC(DATE()) + " | VFP: " + VERSION() + CHR(13) + CHR(10)
*-- Cream fisierul log
STRTOFILE(lcLogHeader, lcLogFile)
RETURN lcLogFile
ENDFUNC
*-- Functie pentru logging cu nivel si timestamp
FUNCTION LogMessage
PARAMETERS cMessage, cLevel, cLogFile
LOCAL lcTimeStamp, lcLogEntry, lcExistingContent
*-- Setam nivel implicit daca nu e specificat
IF EMPTY(cLevel)
cLevel = "INFO "
ELSE
*-- Formatam nivelul pentru a avea 5 caractere
cLevel = LEFT(cLevel + " ", 5)
ENDIF
*-- Cream timestamp-ul
lcTimeStamp = TIME()
*-- Formatam mesajul pentru log
lcLogEntry = "[" + lcTimeStamp + "] [" + cLevel + "] " + cMessage + CHR(13) + CHR(10)
*-- Adaugam la fisierul existent
lcExistingContent = ""
IF FILE(cLogFile)
lcExistingContent = FILETOSTR(cLogFile)
ENDIF
STRTOFILE(lcExistingContent + lcLogEntry, cLogFile)
RETURN .T.
ENDFUNC
*-- Functie pentru inchiderea logging-ului cu statistici
FUNCTION CloseLog
PARAMETERS nStartTime, nProductsCount, nOrdersCount, cLogFile
LOCAL lcEndTime, lcDuration, lcStatsEntry
lcEndTime = TIME()
*-- Calculam durata in secunde
nDuration = SECONDS() - nStartTime
*-- Formatam statisticile finale
lcStatsEntry = "[" + lcEndTime + "] [END ] === GoMag Sync Completed ===" + CHR(13) + CHR(10)
lcStatsEntry = lcStatsEntry + "[" + lcEndTime + "] [STATS] Duration: " + TRANSFORM(INT(nDuration)) + "s"
IF nProductsCount > 0
lcStatsEntry = lcStatsEntry + " | Products: " + TRANSFORM(nProductsCount)
ENDIF
IF nOrdersCount > 0
lcStatsEntry = lcStatsEntry + " | Orders: " + TRANSFORM(nOrdersCount)
ENDIF
lcStatsEntry = lcStatsEntry + CHR(13) + CHR(10)
*-- Adaugam la log
LOCAL lcExistingContent
lcExistingContent = ""
IF FILE(cLogFile)
lcExistingContent = FILETOSTR(cLogFile)
ENDIF
STRTOFILE(lcExistingContent + lcStatsEntry, cLogFile)
RETURN .T.
ENDFUNC