Compare commits

...

5 Commits

Author SHA1 Message Date
4ec125bdfb Enhance logging system with detailed product tracking and console cleanup
- Added comprehensive product logging to match order detail level
- Moved all debug console messages to proper log files
- Added JSON structure analysis logging for both products and orders
- Enhanced per-page progress tracking with counts and warnings
- Unified logging format with [PRODUCTS] and [ORDERS] prefixes
- Removed SET STEP ON debug statement
- All debug information now properly logged instead of console output

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-28 00:22:38 +03:00
1bb830f21c Revert to stable JSON processing with improved logging
Reverted to commit 8324a26 which used:
- nfJsonRead() for JSON deserialization
- MergeProducts/MergeOrdersArray for object-based merging
- nfJsonCreate() for JSON serialization
- SaveProductsArray/SaveOrdersArray procedures

Added improvements:
- Structured logging with timestamps and levels
- Output directory creation
- Statistics tracking (gnProductsProcessed/gnOrdersProcessed)
- CloseLog with duration and count summary

This version should correctly save all 812 products and 399 orders.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-27 17:24:18 +03:00
6f362f41e7 Backup: JSON optimization attempt with direct string merge
This version attempted to optimize JSON processing by:
- Direct string concatenation without deserialization
- MergeProductsJsonDirect and MergeOrdersJsonDirect functions
- Direct STRTOFILE instead of nfJsonCreate

Issues found:
- Only 100 products saved instead of 812 (merge failed)
- Invalid JSON syntax in orders: 'discounts':[,
- Pattern matching errors for GoMag API structure

Reverting to stable version 8324a26 next.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-27 17:22:05 +03:00
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
11 changed files with 1601 additions and 407 deletions

10
.gitignore vendored
View File

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

125
CLAUDE.md
View File

@@ -4,28 +4,40 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Project Overview ## Project Overview
This is a Visual FoxPro 9 project that interfaces with the GoMag e-commerce API. The main component is a script for retrieving product data from GoMag's REST API endpoints. This is a Visual FoxPro 9 project that interfaces with the GoMag e-commerce API. The application retrieves both product and order data from GoMag's REST API endpoints with full pagination support and comprehensive error handling.
## Architecture ## Architecture
- **Single File Application**: `gomag-vending.prg` - Main Visual FoxPro script - **Main Application**: `gomag-vending.prg` - Primary Visual FoxPro script with pagination support
- **Utility Module**: `utils.prg` - INI file handling, logging, and helper functions
- **JSON Library**: `nfjson/` - Third-party JSON parsing library for VFP
- **Technology**: Visual FoxPro 9 with WinHttp.WinHttpRequest.5.1 for HTTP requests - **Technology**: Visual FoxPro 9 with WinHttp.WinHttpRequest.5.1 for HTTP requests
- **API Integration**: GoMag REST API v1 for product management - **API Integration**: GoMag REST API v1 for products and orders management
## Core Components ## Core Components
### gomag-vending.prg ### gomag-vending.prg
Main script that handles: Main application script with:
- GoMag API authentication using Apikey and ApiShop headers - Complete pagination support for products and orders
- HTTP GET requests to retrieve product data - Configurable API settings via INI file
- JSON response parsing and analysis - Comprehensive error handling and logging
- File output for API responses (timestamped .json files) - Rate limiting compliance (1-second delays between requests)
- Error handling and connectivity testing - JSON array output generation for both products and orders
### Key Configuration Variables ### utils.prg
- `lcApiUrl`: GoMag API endpoint (defaults to product read endpoint) Utility functions module containing:
- `lcApiKey`: GoMag API key (must be configured) - INI file operations (`ReadPini`, `WritePini`, `LoadSettings`)
- `lcApiShop`: Shop URL (must be configured) - Logging system (`InitLog`, `LogMessage`, `CloseLog`)
- Connectivity testing (`TestConnectivity`)
- URL encoding utilities (`UrlEncode`)
- Default configuration creation (`CreateDefaultIni`)
### settings.ini
Configuration file with sections:
- `[API]` - API endpoints, credentials, and headers
- `[PAGINATION]` - Page size limits
- `[OPTIONS]` - Feature toggles for products/orders retrieval
- `[FILTERS]` - Date range filters for orders
## Development Commands ## Development Commands
@@ -34,38 +46,85 @@ Main script that handles:
DO gomag-vending.prg DO gomag-vending.prg
``` ```
### Testing Connectivity ### Running from Windows Command Line
The script includes a `TestConnectivity()` function for internet connectivity testing. Use the provided batch file:
```cmd
run-gomag.bat
```
Direct execution with Visual FoxPro:
```cmd
"C:\Program Files (x86)\Microsoft Visual FoxPro 9\vfp9.exe" -T "gomag-vending.prg"
```
## Configuration Management
The application uses `settings.ini` for all configuration. Key settings:
### Required Configuration
- `ApiKey` - Your GoMag API key
- `ApiShop` - Your shop URL (e.g., "https://yourstore.gomag.ro")
### Feature Control
- `GetProducts` - Set to "1" to retrieve products, "0" to skip
- `GetOrders` - Set to "1" to retrieve orders, "0" to skip
- `OrderDaysBack` - Number of days back to retrieve orders (default: 7)
### Pagination
- `Limit` - Records per page (default: 100, max recommended for GoMag API)
## API Integration Details ## API Integration Details
### Authentication ### Authentication
- Uses header-based authentication with `Apikey` and `ApiShop` headers - Header-based authentication using `Apikey` and `ApiShop` headers
- Requires User-Agent to be different from "PostmanRuntime" - User-Agent must differ from "PostmanRuntime"
### Endpoints Used ### Endpoints
- Primary: `https://api.gomag.ro/api/v1/product/read/json?enabled=1` - Products: `https://api.gomag.ro/api/v1/product/read/json?enabled=1`
- Supports pagination, filtering by category/brand, and sorting parameters - Orders: `https://api.gomag.ro/api/v1/order/read/json`
### Rate Limiting ### Rate Limiting
- No specific limitations for READ requests - 1-second pause between paginated requests
- POST requests limited to ~1 request per second (Leaky Bucket algorithm) - No specific READ request limitations
- POST requests limited to ~1 request per second (Leaky Bucket)
## Directory Structure
## File Structure
``` ```
/ /
├── gomag-vending.prg # Main application script ├── gomag-vending.prg # Main application
── gomag_products_*.json # Generated API response files (timestamped) ── utils.prg # Utility functions
├── settings.ini # Configuration file
├── run-gomag.bat # Windows launcher
├── nfjson/ # JSON parsing library
│ ├── nfjsoncreate.prg
│ └── nfjsonread.prg
├── log/ # Generated log files
├── output/ # Generated JSON files
└── products-example.json # Sample API response
``` ```
## Configuration Requirements ## Output Files
Before running, update these variables in `gomag-vending.prg:10-15`: ### Generated Files
1. `lcApiKey` - Your GoMag API key - `log/gomag_sync_YYYYMMDD_HHMMSS.log` - Detailed execution logs
2. `lcApiShop` - Your shop URL (e.g., "https://yourstore.gomag.ro") - `output/gomag_all_products_YYYYMMDD_HHMMSS.json` - Complete product array
- `output/gomag_orders_last7days_YYYYMMDD_HHMMSS.json` - Orders array
## Helper Functions ### Error Files
- `gomag_error_pageN_*.json` - Raw API responses for failed parsing
- `gomag_error_pageN_*.log` - HTTP error details with status codes
- `ParseJsonResponse()` - Basic JSON structure analysis ## Key Functions
- `TestConnectivity()` - Internet connectivity testing
- `UrlEncode()` - URL parameter encoding utility ### Main Application (gomag-vending.prg)
- `SaveProductsArray` - Converts paginated product data to JSON array
- `SaveOrdersArray` - Converts paginated order data to JSON array
- `MergeProducts` - Combines products from multiple pages
- `MergeOrdersArray` - Combines orders from multiple pages
### Utilities (utils.prg)
- `LoadSettings` - Loads complete INI configuration into object
- `InitLog`/`LogMessage`/`CloseLog` - Comprehensive logging system
- `TestConnectivity` - Internet connection verification
- `CreateDefaultIni` - Generates template configuration file

View File

@@ -1,373 +0,0 @@
*-- Script Visual FoxPro 9 pentru accesul la GoMag API cu paginare completa
*-- Autor: Claude AI
*-- Data: 26.08.2025
*-- Setari principale
LOCAL lcApiBaseUrl, lcApiUrl, lcApiKey, lcUserAgent, lcContentType
LOCAL loHttp, lcResponse, lcJsonResponse
LOCAL laHeaders[10], lnHeaderCount
Local lcApiShop, lcCsvFileName, lcErrorResponse, lcFileName, lcLogContent, lcLogFileName, lcPath
Local lcStatusText, lnStatusCode, loError
Local lnLimit, lnCurrentPage, llHasMorePages, loAllJsonData, lnTotalPages, lnTotalProducts
PRIVATE gcAppPath, loJsonData
gcAppPath = ADDBS(JUSTPATH(SYS(16,0)))
SET DEFAULT TO (m.gcAppPath)
lcPath = gcAppPath + 'nfjson;'
SET PATH TO (m.lcPath) ADDITIVE
SET PROCEDURE TO nfjsonread.prg ADDITIVE
*-- Configurare API - MODIFICA aceste valori conform documentatiei GoMag
lcApiBaseUrl = "https://api.gomag.ro/api/v1/product/read/json?enabled=1" && URL de baza pentru lista de produse
lcApiKey = "4c5e46df8f6c4f054fe2787de7a13d4a" && Cheia ta API de la GoMag
lcApiShop = "https://www.coffeepoint.ro" && URL-ul magazinului tau (ex: http://yourdomain.gomag.ro)
lcUserAgent = "Mozilla/5.0" && User-Agent diferit de PostmanRuntime conform documentatiei
lcContentType = "application/json"
lnLimit = 100 && Numarul maxim de produse per pagina (1-100)
lnCurrentPage = 1 && Pagina de start
llHasMorePages = .T. && Flag pentru paginare
loAllJsonData = NULL && Obiect pentru toate datele
*-- Verificare daca avem WinHttp disponibil
TRY
loHttp = CREATEOBJECT("WinHttp.WinHttpRequest.5.1")
CATCH TO loError
? "Eroare la crearea obiectului WinHttp: " + loError.Message
RETURN .F.
ENDTRY
*-- Bucla pentru preluarea tuturor produselor (paginare)
loAllJsonData = CREATEOBJECT("Empty")
ADDPROPERTY(loAllJsonData, "products", CREATEOBJECT("Empty"))
ADDPROPERTY(loAllJsonData, "total", 0)
ADDPROPERTY(loAllJsonData, "pages", 0)
lnTotalProducts = 0
DO WHILE llHasMorePages
*-- Construire URL cu paginare
lcApiUrl = lcApiBaseUrl + "&page=" + TRANSFORM(lnCurrentPage) + "&limit=" + TRANSFORM(lnLimit)
? "Preluare pagina " + TRANSFORM(lnCurrentPage) + "..."
*-- 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)
*-- Prima pagina - setam informatiile generale
IF lnCurrentPage = 1
IF TYPE('loJsonData.total') = 'C' OR TYPE('loJsonData.total') = 'N'
loAllJsonData.total = VAL(TRANSFORM(loJsonData.total))
ENDIF
IF TYPE('loJsonData.pages') = 'C' OR TYPE('loJsonData.pages') = 'N'
loAllJsonData.pages = VAL(TRANSFORM(loJsonData.pages))
ENDIF
? "Total produse: " + TRANSFORM(loAllJsonData.total)
? "Total pagini: " + TRANSFORM(loAllJsonData.pages)
ENDIF
*-- Adaugare produse din pagina curenta
IF TYPE('loJsonData.products') = 'O'
DO MergeProducts WITH loAllJsonData, loJsonData
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 produse
IF TYPE('loJsonData.products') != 'O'
llHasMorePages = .F.
ENDIF
ENDIF
lnCurrentPage = lnCurrentPage + 1
ELSE
*-- Salvare raspuns JSON raw in caz de eroare de parsare
lcFileName = "gomag_error_page" + TRANSFORM(lnCurrentPage) + "_" + DTOS(DATE()) + "_" + STRTRAN(TIME(), ":", "") + ".json"
STRTOFILE(lcResponse, lcFileName)
llHasMorePages = .F.
ENDIF
ELSE
*-- Eroare HTTP - salvare in fisier de log
lcLogFileName = "gomag_error_page" + TRANSFORM(lnCurrentPage) + "_" + DTOS(DATE()) + "_" + STRTRAN(TIME(), ":", "") + ".log"
lcLogContent = "HTTP Error " + TRANSFORM(lnStatusCode) + ": " + lcStatusText + CHR(13) + CHR(10)
*-- Incearca sa citesti raspunsul pentru detalii despre eroare
TRY
lcErrorResponse = loHttp.ResponseText
IF !EMPTY(lcErrorResponse)
lcLogContent = lcLogContent + "Error Details:" + CHR(13) + CHR(10) + lcErrorResponse
ENDIF
CATCH
lcLogContent = lcLogContent + "Could not read error details"
ENDTRY
STRTOFILE(lcLogContent, lcLogFileName)
llHasMorePages = .F.
ENDIF
CATCH TO loError
*-- Salvare erori in fisier de log pentru pagina curenta
lcLogFileName = "gomag_error_page" + TRANSFORM(lnCurrentPage) + "_" + DTOS(DATE()) + "_" + STRTRAN(TIME(), ":", "") + ".log"
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.
ENDTRY
*-- Pauza scurta intre cereri pentru a evita rate limiting
IF llHasMorePages
INKEY(1) && Pauza de 1 secunda
ENDIF
ENDDO
*-- Creare fisier CSV cu toate produsele
IF !ISNULL(loAllJsonData) AND TYPE('loAllJsonData.products') = 'O'
lcCsvFileName = "gomag_all_products_" + DTOS(DATE()) + "_" + STRTRAN(TIME(), ":", "") + ".csv"
DO CreateCsvFromJson WITH loAllJsonData, lcCsvFileName
? "Fisier CSV creat: " + lcCsvFileName
*-- Salvare si a datelor JSON complete
lcJsonFileName = "gomag_all_products_" + DTOS(DATE()) + "_" + STRTRAN(TIME(), ":", "") + ".json"
DO SaveCompleteJson WITH loAllJsonData, lcJsonFileName
? "Fisier JSON complet creat: " + lcJsonFileName
ENDIF
*-- Curatare
loHttp = NULL
*-- Functie pentru unirea produselor din toate paginile
PROCEDURE MergeProducts
PARAMETERS tloAllData, tloPageData
LOCAL lnPropCount, lnIndex, lcPropName, loProduct
*-- Verifica daca avem produse in pagina curenta
IF TYPE('tloPageData.products') = 'O'
*-- Itereaza prin toate produsele din pagina
lnPropCount = AMEMBERS(laPageProducts, tloPageData.products, 0)
FOR lnIndex = 1 TO lnPropCount
lcPropName = laPageProducts(lnIndex)
loProduct = EVALUATE('tloPageData.products.' + lcPropName)
IF TYPE('loProduct') = 'O'
*-- Adauga produsul la colectia principala
ADDPROPERTY(tloAllData.products, lcPropName, loProduct)
ENDIF
ENDFOR
ENDIF
ENDPROC
*-- Functie pentru salvarea datelor JSON complete
PROCEDURE SaveCompleteJson
PARAMETERS tloJsonData, tcFileName
LOCAL lcJsonContent
*-- Construieste JSON simplu pentru salvare
lcJsonContent = '{' + CHR(13) + CHR(10)
lcJsonContent = lcJsonContent + ' "total": ' + TRANSFORM(tloJsonData.total) + ',' + CHR(13) + CHR(10)
lcJsonContent = lcJsonContent + ' "pages": ' + TRANSFORM(tloJsonData.pages) + ',' + CHR(13) + CHR(10)
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
lcPropName = laProducts(lnIndex)
loProduct = EVALUATE('tloJsonData.products.' + lcPropName)
IF TYPE('loProduct') = 'O'
*-- Extrage datele produsului
lcCsvRow = ;
IIF(TYPE('loProduct.id')='C', STRTRAN(loProduct.id, ',', ';'), '') + ',' +;
IIF(TYPE('loProduct.sku')='C', STRTRAN(loProduct.sku, ',', ';'), '') + ',' +;
IIF(TYPE('loProduct.name')='C', '"' + STRTRAN(STRTRAN(loProduct.name, '"', '""'), ',', ';') + '"', '') + ',' +;
IIF(TYPE('loProduct.brand')='C', STRTRAN(loProduct.brand, ',', ';'), '') + ',' +;
IIF(TYPE('loProduct.weight')='C', loProduct.weight, IIF(TYPE('loProduct.weight')='N', TRANSFORM(loProduct.weight), '')) + ',' +;
IIF(TYPE('loProduct.stock')='C', loProduct.stock, IIF(TYPE('loProduct.stock')='N', TRANSFORM(loProduct.stock), '')) + ',' +;
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
ENDIF
ENDFOR
ENDIF
*-- Salvare fisier CSV
STRTOFILE(lcCsvContent, tcCsvFileName)
? "CSV salvat cu " + TRANSFORM(lnPropCount) + " produse"
ENDPROC
*-- Functii helper pentru testare (optionale)
*-- 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
*-- Scriptul cu paginare completa pentru preluarea tuturor produselor
*-- Caracteristici principale:
*-- - Paginare automata pentru toate produsele (100 per pagina)
*-- - Pauze intre cereri pentru respectarea rate limiting
*-- - Creare fisier CSV cu toate produsele
*-- - Salvare fisier JSON complet cu toate datele
*-- - Logging separat pentru fiecare pagina in caz de eroare
*-- - Afisare progres in timpul executiei
*-- INSTRUCTIUNI DE UTILIZARE:
*-- 1. Modifica lcApiKey cu cheia ta API de la GoMag
*-- 2. Modifica lcApiShop cu URL-ul magazinului tau
*-- 3. Ruleaza scriptul - va prelua automat toate produsele
*-- 4. Verifica fisierele generate: CSV si JSON cu toate produsele
*-- Script completat cu paginare - verificati fisierele generate

602
gomag-vending.prg Normal file
View File

@@ -0,0 +1,602 @@
*-- Script Visual FoxPro 9 pentru accesul la GoMag API cu paginare completa
*-- Autor: Claude AI
*-- Data: 26.08.2025
SET SAFETY OFF
SET CENTURY ON
SET DATE DMY
SET EXACT ON
SET ANSI ON
SET DELETED ON
*-- Setari principale
LOCAL lcApiBaseUrl, lcApiUrl, lcApiKey, lcUserAgent, lcContentType
LOCAL loHttp, lcResponse, lcJsonResponse
LOCAL laHeaders[10], lnHeaderCount
Local lcApiShop, lcCsvFileName, lcErrorResponse, lcFileName, lcLogContent, lcLogFileName, lcPath
Local lcStatusText, lnStatusCode, loError
Local lnLimit, lnCurrentPage, llHasMorePages, loAllJsonData, lnTotalPages, lnTotalProducts
Local lcOrderApiUrl, loAllOrderData, lcOrderCsvFileName, lcOrderJsonFileName
Local ldStartDate, lcStartDateStr
Local lcIniFile, loSettings
LOCAL llGetProducts, llGetOrders
PRIVATE gcAppPath, loJsonData, gcLogFile, gnStartTime, gnProductsProcessed, gnOrdersProcessed
gcAppPath = ADDBS(JUSTPATH(SYS(16,0)))
SET DEFAULT TO (m.gcAppPath)
lcPath = gcAppPath + 'nfjson;'
SET PATH TO (m.lcPath) ADDITIVE
SET PROCEDURE TO utils.prg ADDITIVE
SET PROCEDURE TO nfjsonread.prg ADDITIVE
SET PROCEDURE TO nfjsoncreate.prg ADDITIVE
*-- Initializare logging si statistici
gnStartTime = SECONDS()
gnProductsProcessed = 0
gnOrdersProcessed = 0
gcLogFile = InitLog("gomag_sync")
*-- 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
llHasMorePages = .T. && Flag pentru paginare
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
TRY
loHttp = CREATEOBJECT("WinHttp.WinHttpRequest.5.1")
CATCH TO loError
LogMessage("Eroare la crearea obiectului WinHttp: " + loError.Message, "ERROR", gcLogFile)
RETURN .F.
ENDTRY
*-- SECTIUNEA PRODUSE - se executa doar daca llGetProducts = .T.
IF llGetProducts
LogMessage("[PRODUCTS] Starting product retrieval", "INFO", gcLogFile)
*-- Bucla pentru preluarea tuturor produselor (paginare)
loAllJsonData = CREATEOBJECT("Empty")
ADDPROPERTY(loAllJsonData, "products", CREATEOBJECT("Empty"))
ADDPROPERTY(loAllJsonData, "total", 0)
ADDPROPERTY(loAllJsonData, "pages", 0)
lnTotalProducts = 0
DO WHILE llHasMorePages
*-- Construire URL cu paginare
lcApiUrl = lcApiBaseUrl + "&page=" + TRANSFORM(lnCurrentPage) + "&limit=" + TRANSFORM(lnLimit)
LogMessage("[PRODUCTS] 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)
*-- Prima pagina - setam informatiile generale
IF lnCurrentPage = 1
LogMessage("[PRODUCTS] Analyzing JSON structure...", "INFO", gcLogFile)
LOCAL ARRAY laJsonProps[1]
lnPropCount = AMEMBERS(laJsonProps, loJsonData, 0)
FOR lnDebugIndex = 1 TO MIN(lnPropCount, 10) && Primele 10 proprietati
lcPropName = laJsonProps(lnDebugIndex)
lcPropType = TYPE('loJsonData.' + lcPropName)
LogMessage("[PRODUCTS] Property: " + lcPropName + " (Type: " + lcPropType + ")", "DEBUG", gcLogFile)
ENDFOR
IF TYPE('loJsonData.total') = 'C' OR TYPE('loJsonData.total') = 'N'
loAllJsonData.total = VAL(TRANSFORM(loJsonData.total))
ENDIF
IF TYPE('loJsonData.pages') = 'C' OR TYPE('loJsonData.pages') = 'N'
loAllJsonData.pages = VAL(TRANSFORM(loJsonData.pages))
ENDIF
LogMessage("[PRODUCTS] Total items: " + TRANSFORM(loAllJsonData.total) + " | Pages: " + TRANSFORM(loAllJsonData.pages), "INFO", gcLogFile)
ENDIF
*-- Adaugare produse din pagina curenta
LOCAL llHasProducts, lnProductsFound
llHasProducts = .F.
lnProductsFound = 0
IF TYPE('loJsonData.products') = 'O'
*-- Numaram produsele din obiectul products
lnProductsFound = AMEMBERS(laProductsPage, loJsonData.products, 0)
IF lnProductsFound > 0
DO MergeProducts WITH loAllJsonData, loJsonData
llHasProducts = .T.
LogMessage("[PRODUCTS] Found: " + TRANSFORM(lnProductsFound) + " products in page " + TRANSFORM(lnCurrentPage), "INFO", gcLogFile)
gnProductsProcessed = gnProductsProcessed + lnProductsFound
ENDIF
ENDIF
IF !llHasProducts
LogMessage("[PRODUCTS] WARNING: No products 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 produse
IF TYPE('loJsonData.products') != 'O'
llHasMorePages = .F.
ENDIF
ENDIF
lnCurrentPage = lnCurrentPage + 1
ELSE
*-- Salvare raspuns JSON raw in caz de eroare de parsare
lcFileName = "gomag_error_page" + TRANSFORM(lnCurrentPage) + "_" + DTOS(DATE()) + "_" + STRTRAN(TIME(), ":", "") + ".json"
STRTOFILE(lcResponse, lcFileName)
llHasMorePages = .F.
ENDIF
ELSE
*-- Eroare HTTP - salvare in fisier de log
lcLogFileName = "gomag_error_page" + TRANSFORM(lnCurrentPage) + "_" + DTOS(DATE()) + "_" + STRTRAN(TIME(), ":", "") + ".log"
lcLogContent = "HTTP Error " + TRANSFORM(lnStatusCode) + ": " + lcStatusText + CHR(13) + CHR(10)
*-- Incearca sa citesti raspunsul pentru detalii despre eroare
TRY
lcErrorResponse = loHttp.ResponseText
IF !EMPTY(lcErrorResponse)
lcLogContent = lcLogContent + "Error Details:" + CHR(13) + CHR(10) + lcErrorResponse
ENDIF
CATCH
lcLogContent = lcLogContent + "Could not read error details"
ENDTRY
STRTOFILE(lcLogContent, lcLogFileName)
llHasMorePages = .F.
ENDIF
CATCH TO loError
*-- Salvare erori in fisier de log pentru pagina curenta
lcLogFileName = "gomag_error_page" + TRANSFORM(lnCurrentPage) + "_" + DTOS(DATE()) + "_" + STRTRAN(TIME(), ":", "") + ".log"
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.
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 produsele
IF !ISNULL(loAllJsonData) AND TYPE('loAllJsonData.products') = 'O'
lcJsonFileName = lcOutputDir + "\gomag_all_products_" + DTOS(DATE()) + "_" + STRTRAN(TIME(), ":", "") + ".json"
DO SaveProductsArray WITH loAllJsonData, lcJsonFileName
LogMessage("[PRODUCTS] JSON saved: " + lcJsonFileName, "INFO", gcLogFile)
*-- Calculam numarul de produse procesate
IF TYPE('loAllJsonData.products') = 'O'
LOCAL ARRAY laProducts[1]
lnPropCount = AMEMBERS(laProducts, loAllJsonData.products, 0)
gnProductsProcessed = lnPropCount
ENDIF
ENDIF
ELSE
LogMessage("[PRODUCTS] Skipped product retrieval (llGetProducts = .F.)", "INFO", gcLogFile)
ENDIF
*-- SECTIUNEA COMENZI - se executa doar daca llGetOrders = .T.
IF llGetOrders
LogMessage("[ORDERS] =======================================", "INFO", gcLogFile)
LogMessage("[ORDERS] RETRIEVING ORDERS FROM LAST " + TRANSFORM(loSettings.OrderDaysBack) + " DAYS", "INFO", gcLogFile)
LogMessage("[ORDERS] Start date: " + lcStartDateStr, "INFO", gcLogFile)
LogMessage("[ORDERS] =======================================", "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] Analyzing JSON structure...", "INFO", gcLogFile)
LOCAL ARRAY laJsonProps[1]
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 din metadata
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 orders: " + TRANSFORM(loAllOrderData.total), "INFO", gcLogFile)
LogMessage("[ORDERS] Total pages: " + TRANSFORM(loAllOrderData.pages), "INFO", gcLogFile)
ENDIF
*-- Adaugare comenzi din pagina curenta
*-- API-ul GoMag returneaza obiect cu metadata si orders
LOCAL llHasOrders, lnOrdersFound
llHasOrders = .F.
lnOrdersFound = 0
*-- Verificam daca avem obiectul orders
IF TYPE('loJsonData.orders') = 'O'
*-- Numaram comenzile din obiectul orders
lnOrdersFound = AMEMBERS(laOrdersPage, loJsonData.orders, 0)
IF lnOrdersFound > 0
*-- Mergem comenzile din pagina curenta
DO MergeOrdersArray WITH loAllOrderData, loJsonData
llHasOrders = .T.
LogMessage("[ORDERS] Found: " + TRANSFORM(lnOrdersFound) + " orders in page " + TRANSFORM(lnCurrentPage), "INFO", gcLogFile)
gnOrdersProcessed = gnOrdersProcessed + lnOrdersFound
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 folosind metadata
IF TYPE('loJsonData.pages') = 'C' OR TYPE('loJsonData.pages') = 'N'
lnTotalPages = VAL(TRANSFORM(loJsonData.pages))
IF lnCurrentPage >= lnTotalPages
llHasMorePages = .F.
ENDIF
ELSE
*-- Fallback: verifica daca am primit mai putin decat limita
IF !llHasOrders OR lnOrdersFound < lnLimit
llHasMorePages = .F.
ENDIF
ENDIF
lnCurrentPage = lnCurrentPage + 1
ELSE
*-- Salvare raspuns JSON raw in caz de eroare de parsare
lcFileName = "gomag_order_error_page" + TRANSFORM(lnCurrentPage) + "_" + DTOS(DATE()) + "_" + STRTRAN(TIME(), ":", "") + ".json"
STRTOFILE(lcResponse, lcFileName)
llHasMorePages = .F.
ENDIF
ELSE
*-- Eroare HTTP - salvare in fisier de log
lcLogFileName = "gomag_order_error_page" + TRANSFORM(lnCurrentPage) + "_" + DTOS(DATE()) + "_" + STRTRAN(TIME(), ":", "") + ".log"
lcLogContent = "HTTP Error " + TRANSFORM(lnStatusCode) + ": " + lcStatusText + CHR(13) + CHR(10)
*-- Incearca sa citesti raspunsul pentru detalii despre eroare
TRY
lcErrorResponse = loHttp.ResponseText
IF !EMPTY(lcErrorResponse)
lcLogContent = lcLogContent + "Error Details:" + CHR(13) + CHR(10) + lcErrorResponse
ENDIF
CATCH
lcLogContent = lcLogContent + "Could not read error details"
ENDTRY
STRTOFILE(lcLogContent, lcLogFileName)
llHasMorePages = .F.
ENDIF
CATCH TO loError
*-- Salvare erori in fisier de log pentru pagina curenta
lcLogFileName = "gomag_order_error_page" + TRANSFORM(lnCurrentPage) + "_" + DTOS(DATE()) + "_" + STRTRAN(TIME(), ":", "") + ".log"
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.
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 file created: " + lcOrderJsonFileName, "INFO", gcLogFile)
ENDIF
ELSE
LogMessage("[ORDERS] Skipped order retrieval (llGetOrders = .F.)", "INFO", gcLogFile)
ENDIF
*-- Curatare
loHttp = NULL
*-- 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
PARAMETERS tloAllData, tloPageData
LOCAL lnPropCount, lnIndex, lcPropName, loProduct
*-- Verifica daca avem produse in pagina curenta
IF TYPE('tloPageData.products') = 'O'
*-- Itereaza prin toate produsele din pagina
lnPropCount = AMEMBERS(laPageProducts, tloPageData.products, 0)
FOR lnIndex = 1 TO lnPropCount
lcPropName = laPageProducts(lnIndex)
loProduct = EVALUATE('tloPageData.products.' + lcPropName)
IF TYPE('loProduct') = 'O'
*-- Adauga produsul la colectia principala
ADDPROPERTY(tloAllData.products, lcPropName, loProduct)
ENDIF
ENDFOR
ENDIF
ENDPROC
*-- Functie pentru unirea comenzilor din array direct (structura GoMag)
PROCEDURE MergeOrdersArray
PARAMETERS tloAllData, tloPageData
LOCAL lnPropCount, lnIndex, lcPropName, loOrder
*-- Verifica daca avem comenzi in pagina curenta
IF TYPE('tloPageData.orders') = 'O'
*-- Itereaza prin toate comenzile din pagina (array direct)
lnPropCount = AMEMBERS(laPageOrders, tloPageData.orders, 0)
FOR lnIndex = 1 TO lnPropCount
lcPropName = laPageOrders(lnIndex)
loOrder = EVALUATE('tloPageData.orders.' + lcPropName)
IF TYPE('loOrder') = 'O'
*-- Folosim ID-ul comenzii ca nume proprietate, sau un index secvential
LOCAL lcOrderId
lcOrderId = ""
IF TYPE('loOrder.id') = 'C'
lcOrderId = "order_" + loOrder.id
ELSE
lcOrderId = "order_" + TRANSFORM(lnIndex)
ENDIF
*-- Adauga comanda la colectia principala
ADDPROPERTY(tloAllData.orders, lcOrderId, loOrder)
ENDIF
ENDFOR
ENDIF
ENDPROC
*-- Functiile utilitare au fost mutate in utils.prg
*-- Scriptul cu paginare completa pentru preluarea tuturor produselor si comenzilor
*-- Caracteristici principale:
*-- - Paginare automata pentru toate produsele si comenzile
*-- - Pauze intre cereri pentru respectarea rate limiting
*-- - Salvare JSON array-uri pure (fara metadata de paginare)
*-- - Utilizare nfjsoncreate pentru generare JSON corecta
*-- - Logging separat pentru fiecare pagina in caz de eroare
*-- - Afisare progres in timpul executiei
*-- INSTRUCTIUNI DE UTILIZARE:
*-- 1. Modifica settings.ini cu setarile tale:
*-- - ApiKey: cheia ta API de la GoMag
*-- - ApiShop: URL-ul magazinului tau
*-- - 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 optimizat cu salvare JSON array-uri - verificati fisierele generate

Binary file not shown.

208
orders-example.json Normal file
View File

@@ -0,0 +1,208 @@
{
"total": "399",
"page": "1",
"pages": 4,
"orders": {
"60644": {
"id": "60644",
"number": "436232189",
"date": "2025-08-27 16:32:43",
"invoice": {
"series": "",
"number": "0",
"date": "0000-00-00 00:00:00"
},
"total": "1026.24",
"status": "Comanda noua",
"statusId": "1",
"source": "internal",
"sales_channel": "Website",
"sales_channel_marketplace": "",
"sales_agent": "",
"currency": "RON",
"observation": "",
"payment": {
"name": "Numerar/Ramburs sau Card la easybox",
"online": "0",
"completed": "0"
},
"delivery": {
"name": "Transport gratuit",
"total": 0,
"date": "0000-00-00 00:00:00",
"lockerId": 0
},
"shipping": {
"company": "",
"firstname": "Liviu",
"lastname": "Stefan",
"phone": "0751013764",
"email": "liviustefan2001@gmail.com",
"address": "aleea Soarelui nr 2, casa nr 4, cartier Brates Lake",
"city": "Galați",
"region": "Galati",
"country": "Romania",
"zipcode": null
},
"items": [
{
"id": "582",
"type": "product",
"sku": "8000070028685",
"ean": "8000070028685",
"name": "Lavazza Gusto Forte Vending Cafea Boabe 1kg",
"price": "69.79",
"baseprice": "78",
"vat": "11",
"quantity": "10.00"
},
{
"id": "589",
"type": "product",
"sku": "5941623010333",
"ean": "5941623010333",
"name": "Doncafe Espresso Intense Cafea Boabe 1 kg",
"price": "56.49",
"baseprice": "62",
"vat": "11",
"quantity": "2.00"
},
{
"id": "512",
"type": "product",
"sku": "82",
"ean": "",
"name": "Zahar Plic Lavazza 200buc",
"price": "10.99",
"baseprice": "14",
"vat": "21",
"quantity": "5.00"
},
{
"id": "671",
"type": "product",
"sku": "312349",
"ean": "",
"name": "Palete manuale din lemn 110mm 1000buc",
"price": "7.99",
"baseprice": "10.5",
"vat": "21",
"quantity": "2.00"
},
{
"id": "554",
"type": "product",
"sku": "8004990127091",
"ean": "8004990127091",
"name": "Ristora Ciocolată Instant 1kg",
"price": "25.99",
"baseprice": "28",
"vat": "21",
"quantity": "2.00"
},
{
"id": "567",
"type": "product",
"sku": "8004990125530",
"ean": "8004990125530",
"name": "Prolait Topping Blue Lapte Granulat 500g",
"price": "18.49",
"baseprice": "23",
"vat": "21",
"quantity": "5.00"
}
],
"billing": {
"firstname": "Liviu",
"lastname": "Stefan",
"phone": "0751013764",
"email": "liviustefan2001@gmail.com",
"address": "aleea Soarelui nr 2, casa nr 4, cartier Brates Lake",
"city": "Galați",
"region": "Galati",
"country": "Romania",
"customerId": "5997"
},
"discounts": [
],
"updated": "2025-08-27 16:32:43"
},
"60643": {
"id": "60643",
"number": "436232175",
"date": "2025-08-27 16:19:29",
"invoice": {
"series": "",
"number": "0",
"date": "0000-00-00 00:00:00"
},
"total": "359.4",
"status": "Comanda noua",
"statusId": "1",
"source": "internal",
"sales_channel": "Website",
"sales_channel_marketplace": "",
"sales_agent": "",
"currency": "RON",
"observation": "",
"payment": {
"name": "Numerar/Ramburs sau Card la easybox",
"online": "0",
"completed": "0"
},
"delivery": {
"name": "Transport National",
"total": 30,
"date": "0000-00-00 00:00:00",
"lockerId": 0
},
"shipping": {
"company": "",
"firstname": "Alexandra",
"lastname": "TONE",
"phone": "0763571486",
"email": "aristocratgaminggr@gmail.com",
"address": "Str. Garii, Nr. 102",
"city": "Giurgiu",
"region": "Giurgiu",
"country": "Romania",
"zipcode": null
},
"items": [
{
"id": "279",
"type": "product",
"sku": "30006ozLavazza",
"ean": "",
"name": "Pahar carton 6oz Lavazza RLP bax 3000buc",
"price": "329.4",
"baseprice": "360",
"vat": "21",
"quantity": "1.00"
}
],
"billing": {
"company": {
"name": "ARISTOCRAT GAMING SRL",
"code": "32128076",
"registrationNo": "J27/487/2013",
"bank": "",
"iban": ""
},
"firstname": "Alexandra",
"lastname": "TONE",
"phone": "0763571486",
"email": "aristocratgaminggr@gmail.com",
"address": "Aleea Revolutiei, Spatiul Comercial Nr.32",
"city": "Roman",
"region": "Neamt",
"country": "Romania",
"customerId": "12283"
},
"discounts": [
],
"updated": "2025-08-27 16:19:29"
}
},
"shippingAsProduct": false
}

137
products-example.json Normal file
View File

@@ -0,0 +1,137 @@
{
"total": "Numar total de rezultate",
"page": "Pagina curenta",
"pages": "Numar total de pagini",
"products": {
"id": "ID intern al produsului",
"sku": "SKU",
"name": "Denumire",
"description": "Descriere",
"short_description": "Descriere scurta",
"url": "URL",
"canonical_url": "URL canonic",
"brand": "Marca produs",
"categories": {
"Branch 1": [
{
"id": "ID-ul categoriei",
"name": "Denumirea categoriei"
},
{
"id": "ID-ul categoriei",
"name": "Denumirea categoriei"
}
],
"Branch 2": [
{
"id": "ID-ul categoriei",
"name": "Denumirea categoriei"
},
{
"id": "ID-ul categoriei",
"name": "Denumirea categoriei"
}
],
"...": [
"..."
]
},
"weight": "Greutatea produsului (kg)",
"enabled": "Activ (0/1)",
"parent": "ID produs parinte",
"parent_sku": "SKU produs parinte",
"group_key": "Codul de grupare al variantelor de produs",
"stockManagement": "Gestioneaza automat stocul produsului",
"stock": "Stoc cantitativ",
"stockStatus": "Status stoc",
"base_price": "Pret de baza al produsului",
"price": "Pret de vanzare al produsului (dupa aplicarea adaosurilor si reducerilor)",
"vat_included": "Pret include TVA (0/1)",
"vat": "Valoare TVA",
"currency": "Moneda",
"ecotax": "Valoare taxa verde",
"um": "Unitate de masura",
"html_title": "Titlu html",
"html_description": "Descrierere html",
"customSearchKeys": "Cuvinte cheie folosite la cautarea SEO",
"feedDescription": "Descriere pentru feed-uri",
"allowOrdersWhenOutOfStock": "Produsul se aduce la comanda",
"attributes": [
{
"id": "ID atribut",
"type": "Tip atribut: dropdown, textinput, textarea",
"name": "Denumire atribut",
"value": "Optiune"
},
{
"id": "ID atribut",
"type": "Tip atribut multipleselect (accepta valori multiple)",
"name": "Denumire atribut",
"value": [
"Optiune1",
"Optiune2",
"..."
]
}
],
"images": [
"Imagine 1 (principala)",
"Imagine 2",
"..."
],
"variations": [
{
"id": "ID intern al produsului",
"sku": "SKU",
"base_price": "Pret de baza al produsului",
"price": "Pret de vanzare al produsului (dupa aplicarea adaosurilor si reducerilor)",
"stock": "Stoc cantitativ",
"stockStatus": "Status stoc",
"stockManagement": "Gestioneaza automat stocul produsului",
"versionAttributes": {
"id Attribut": {
"name": "Denumire atribut",
"value": "Valoare atribut"
}
}
},
{
"id": "ID intern al produsului",
"sku": "SKU",
"base_price": "Pret de baza al produsului",
"price": "Pret de vanzare al produsului (dupa aplicarea adaosurilor si reducerilor)",
"stock": "Stoc cantitativ",
"stockStatus": "Status stoc",
"stockManagement": "Gestioneaza automat stocul produsului",
"versionAttributes": {
"id Attribut": {
"id": "ID atribut",
"name": "Denumire atribut",
"value": "Valoare atribut"
}
}
}
],
"ean": "EAN",
"videos": [
"URL video"
],
"files": [
"URL fisiere"
],
"updated": "Ultima modificare",
"created": "Data crearii",
"delivery_time": "Timp de livrare",
"delivery_time_type": "Tip timp de livrare",
"bundleItems": [
{
"sku": "SKU componenta",
"quantity": "Cantitate"
},
{
"sku": "SKU componenta",
"quantity": "Cantitate"
}
]
}
}

268
regex.prg Normal file
View File

@@ -0,0 +1,268 @@
*!* CLEAR
*!* ?strtranx([ana are 1234567890.1234 lei], [\s\d+\.\d\s], [=TRANSFORM($1, "999 999 999 999.99")])
*?strtranx([ana are <<1234567890.1234>> lei], [<<], [=TRANSFORM($1, "AA")])
*!* RETURN
CLEAR
*-- http://www.cornerstonenw.com/article_id_parsing3.htm
SET STEP ON
lcSourceString = [ana are mere 123,345 678 ad]
LOCAL laItems[10]
lnResults = GetRegExpAll(lcSourceString, '\d+', @laItems)
SET STEP ON
RETURN
strTest = [ab cd2""$$<24>]
?strTest
?StripNonAscii(strTest)
*-- replace non a-z09 with "" case-insensitive
? strtranx([Ab ra /ca\d&abr'a],"[^a-z0-9]",[],1,,1)
RETURN
*-- count words
? OccursRegExp("\b(\w+)\b", [the then quick quick brown fox fox])
&& prints 7
*-- count repeatedwords
? OccursRegExp("\b(\w+)\s\1\b", [the then quick quick brown fox fox])
&& prints 2
*-- replace first and second lower-case "a"
? strtranx([Abracadabra],[a],[*],1,2)
&& prints Abr*c*dabra
*-- replace first and second "a" case-insensitive
? strtranx([Abracadabra],[a],[*],1,2,1)
&& prints *br*cadabra
*-- locate the replacement targets
? strtranx([Abracadabra],[^a|a$],[*],1,2,0)
&& Abracadabr*
? strtranx([Abracadabra],[^a|a$],[*],1,2,1)
&& *bracadabr*
lcText = "The cost, is $123,345.75. "
*-- convert the commas
lcText = strtranx( m.lcText, "(\d{1,3})\,(\d{1,}) ","$1 $2" )
*-- convert the decimals
? strtranx( m.lcText, "(\d{1,3})\.(\d{1,})", "$1,$2" )
** prints "The cost, is $123 345,75."
*-- add 1 to all digits
? strtranx( [ABC123], "(\d)", [=TRANSFORM(VAL($1)+1)] )
** prints "ABC234"
*-- convert all dates to long format
? strtranx( [the date is: 7/18/2004 ] , [(\d{1,2}/\d{1,2}/\d{4})], [=TRANSFORM(CTOD($1),"@YL")])
** prints "the date is: Sunday, July 18, 2004"
*----------------------------------------------------------
FUNCTION StrtranRegExp( tcSourceString, tcPattern, tcReplace )
LOCAL loRE
loRE = CREATEOBJECT("vbscript.regexp")
WITH loRE
.PATTERN = tcPattern
.GLOBAL = .T.
.multiline = .T.
RETURN .REPLACE( tcSourceString , tcReplace )
ENDWITH
ENDFUNC
*----------------------------------------------------------
FUNCTION OccursRegExp(tcPattern, tcText)
LOCAL loRE, loMatches, lnResult
loRE = CREATEOBJECT("vbscript.regexp")
WITH loRE
.PATTERN = m.tcPattern
.GLOBAL = .T.
.multiline = .T.
loMatches = loRE.Execute( m.tcText )
lnResult = loMatches.COUNT
loMatches = NULL
ENDWITH
RETURN m.lnResult
ENDFUNC
*----------------------------------------------------------
FUNCTION strtranx(tcSearched, ;
tcSearchFor, ;
tcReplacement, ;
tnStart, tnNumber, ;
tnFlag )
*-- the final version of the UDF
LOCAL loRE, lcText, lnShift, lcCommand,;
loMatch, loMatches, lnI, lnK, lcSubMatch,;
llevaluate, lcMatchDelim, lcReplaceText, lcReplacement,;
lnStart, lnNumber, loCol, lcKey
IF EMPTY(NVL(tcSearched, ''))
RETURN NVL(tcSearched, '')
ENDIF
loRE = CREATEOBJECT("vbscript.regexp")
WITH loRE
.PATTERN = m.tcSearchFor
.GLOBAL = .T.
.multiline = .T.
.ignorecase = IIF(VARTYPE(m.tnFlag)=[N],m.tnFlag = 1,.F.)
ENDWITH
lcReplacement = m.tcReplacement
*--- are we evaluating?
IF m.lcReplacement = [=]
llevaluate = .T.
lcReplacement = SUBSTR( m.lcReplacement, 2 )
ENDIF
IF VARTYPE( m.tnStart )=[N]
lnStart = m.tnStart
ELSE
lnStart = 1
ENDIF
IF VARTYPE( m.tnNumber) =[N]
lnNumber = m.tnNumber
ELSE
lnNumber = -1
ENDIF
IF m.lnStart>1 OR m.lnNumber#-1 OR m.llevaluate
lcText = m.tcSearched
lnShift = 1
loMatches = loRE.execute( m.lcText )
loCol = CREATEOBJECT([collection])
lnNumber = IIF( lnNumber=-1,loMatches.COUNT,MIN(lnNumber,loMatches.COUNT))
FOR lnK = m.lnStart TO m.lnNumber
loMatch = loMatches.ITEM(m.lnK-1) && zero based
lcCommand = m.lcReplacement
FOR lnI= 1 TO loMatch.submatches.COUNT
lcSubMatch = loMatch.submatches(m.lnI-1) && zero based
IF m.llevaluate
* "escape" the string we are about to use in an evaluation.
* it is important to escape due to possible delim chars (like ", ' etc)
* malicious content, or VFP line-length violations.
lcKey = ALLTRIM(TRANSFORM(m.lnK)+[_]+TRANSFORM(m.lnI))
loCol.ADD( m.lcSubMatch, m.lcKey )
lcSubMatch = [loCol.item(']+m.lcKey+[')]
ENDIF
lcCommand = STRTRAN( m.lcCommand, "$" + ALLTRIM( STR( m.lnI ) ) , m.lcSubMatch)
ENDFOR
IF m.llevaluate
TRY
lcReplaceText = EVALUATE( m.lcCommand )
CATCH TO loErr
lcReplaceText="[[ERROR #"+TRANSFORM(loErr.ERRORNO)+[ ]+loErr.MESSAGE+"]]"
ENDTRY
ELSE
lcReplaceText = m.lcCommand
ENDIF
lcText = STUFF( m.lcText, loMatch.FirstIndex + m.lnShift, m.loMatch.LENGTH, m.lcReplaceText )
lnShift = m.lnShift + LEN( m.lcReplaceText ) - m.loMatch.LENGTH
ENDFOR
ELSE
lcText = loRE.REPLACE( m.tcSearched, m.tcReplacement )
ENDIF
RETURN m.lcText
ENDFUNC
*=====================
FUNCTION StripNonAscii
LPARAMETERS tcSourceString, tcReplaceString
TEXT TO lcPattern NOSHOW
[^A-Za-z 0-9 \.,\?'""!@#\$%\^&\*\(\)-_=\+;:<>\/\\\|\}\{\[\]`~]
ENDTEXT
lcReplace = IIF(TYPE('tcReplaceString') <> 'C', "", tcReplaceString)
lcReturn = strtranx( m.tcSourceString, m.lcPattern, m.lcReplace,1,,1)
RETURN m.lcReturn
ENDFUNC && StripNonAscii
*=====================
* Intoarce un text care se potriveste cu pattern-ul
* Ex. Localitatea din textul: STRADA NR LOCALITATE
*=====================
FUNCTION GetRegExp
LPARAMETERS tcSourceString, tcPattern, tnOccurence
* tcSourceString: Bld. Stefan cel Mare 14 Tirgu Neamt
* tcPattern: [A-Za-z\s]+$ = (caracter sau spatiu) de cel putin o data la sfarsitul liniei = Tirgu Neamt
* tcPattern: \d+[A-Za-z\s]+$ = oricate cifre (caracter sau spatiu) de cel putin o data la sfarsitul liniei = 14 Tirgu Neamt
LOCAL loRE, loMatches, lcResult, lnOccurence
lcResult = ''
lnOccurence = IIF(!EMPTY(m.tnOccurence) and TYPE('tnOccurence') = 'N', m.tnOccurence, 1)
loRE = CREATEOBJECT("vbscript.regexp")
WITH loRE
.PATTERN = m.tcPattern
.GLOBAL = .T.
.multiline = .T.
loMatches = loRE.Execute( m.tcSourceString)
IF loMatches.COUNT >= m.lnOccurence
lcResult = loMatches.Item(m.lnOccurence - 1).Value
ENDIF
loMatches = NULL
ENDWITH
RETURN m.lcResult
ENDFUNC && GetRegExp
*=====================
* Intoarce numarul potrivirilor si un parametru OUT array sau lista de numere facturi separate prin ","
* Ex. Toate numerele dintr-un text lnMatches = GetRegExpAll(lcSourceString, '\d+', @loMatches)
*=====================
FUNCTION GetRegExpAll
LPARAMETERS tcSourceString, tcPattern, taItems
* tcSourceString: Bld. Stefan cel Mare 14 Tirgu Neamt
* tcPattern: [A-Za-z\s]+$ = (caracter sau spatiu) de cel putin o data la sfarsitul liniei = Tirgu Neamt
* tcPattern: \d+[A-Za-z\s]+$ = oricate cifre (caracter sau spatiu) de cel putin o data la sfarsitul liniei = 14 Tirgu Neamt
* taItems "A">taItems : array cu rezultatele (OUT) taItems[1..Result] sau taItems "C" lista facturi separate prin virgula
LOCAL loRE, loMatches, lnResults, lnItem
IF TYPE('taItems') = "A"
EXTERNAL ARRAY taItems
ELSE
taItems = ""
ENDIF
lnResult = 0
loRE = CREATEOBJECT("vbscript.regexp")
WITH loRE
.PATTERN = m.tcPattern
.GLOBAL = .T.
.multiline = .T.
loMatches = loRE.Execute( m.tcSourceString)
lnResults = loMatches.COUNT
IF TYPE('taItems') = "A"
DIMENSION taItems[m.lnResult]
FOR lnItem = 1 TO m.lnResult
taItems[m.lnItem] = loMatches.Item(m.lnItem-1).Value
ENDFOR
ELSE
FOR lnItem = 1 TO m.lnResults
taItems = taItems + IIF(m.lnItem > 1, ",", "") + loMatches.Item(m.lnItem-1).Value
ENDFOR
ENDIF
loMatches = NULL
ENDWITH
RETURN m.lnResults
ENDFUNC && GetRegExp

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