Compare commits

..

3 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
7 changed files with 786 additions and 98 deletions

3
.gitignore vendored
View File

@@ -3,7 +3,8 @@
*.bak *.bak
*.BAK *.BAK
*.csv *.csv
*.json /log.*
/output/*.json
*.err *.err
*.ERR *.ERR
*.log *.log

121
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
@@ -35,51 +47,84 @@ DO gomag-vending.prg
``` ```
### Running from Windows Command Line ### Running from Windows Command Line
Use the provided batch file for easy execution: Use the provided batch file:
```cmd ```cmd
run-gomag.bat run-gomag.bat
``` ```
Or directly with Visual FoxPro executable: Direct execution with Visual FoxPro:
```cmd ```cmd
"C:\Program Files (x86)\Microsoft Visual FoxPro 9\vfp9.exe" -T "path\to\gomag-vending-test.prg" "C:\Program Files (x86)\Microsoft Visual FoxPro 9\vfp9.exe" -T "gomag-vending.prg"
``` ```
The batch file uses `%~dp0` to automatically detect the current directory, making it portable across different locations. ## Configuration Management
### Testing Connectivity The application uses `settings.ini` for all configuration. Key settings:
The script includes a `TestConnectivity()` function for internet connectivity testing.
### 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-test.prg # Main application script ├── gomag-vending.prg # Main application
├── run-gomag.bat # Windows batch file for easy execution ├── utils.prg # Utility functions
── gomag_products_*.json # Generated API response files (timestamped) ── 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

@@ -3,10 +3,11 @@
*-- Data: 26.08.2025 *-- Data: 26.08.2025
SET SAFETY OFF SET SAFETY OFF
SET EXACT ON
SET CENTURY ON SET CENTURY ON
SET DELETED ON
SET DATE DMY SET DATE DMY
SET EXACT ON
SET ANSI ON
SET DELETED ON
*-- Setari principale *-- Setari principale
LOCAL lcApiBaseUrl, lcApiUrl, lcApiKey, lcUserAgent, lcContentType LOCAL lcApiBaseUrl, lcApiUrl, lcApiKey, lcUserAgent, lcContentType
@@ -28,8 +29,8 @@ SET DEFAULT TO (m.gcAppPath)
lcPath = gcAppPath + 'nfjson;' lcPath = gcAppPath + 'nfjson;'
SET PATH TO (m.lcPath) ADDITIVE SET PATH TO (m.lcPath) ADDITIVE
SET PROCEDURE TO nfjsonread.prg ADDITIVE
SET PROCEDURE TO utils.prg ADDITIVE SET PROCEDURE TO utils.prg ADDITIVE
SET PROCEDURE TO nfjsonread.prg ADDITIVE
SET PROCEDURE TO nfjsoncreate.prg ADDITIVE SET PROCEDURE TO nfjsoncreate.prg ADDITIVE
*-- Initializare logging si statistici *-- Initializare logging si statistici
@@ -103,7 +104,6 @@ CATCH TO loError
LogMessage("Eroare la crearea obiectului WinHttp: " + loError.Message, "ERROR", gcLogFile) 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. *-- SECTIUNEA PRODUSE - se executa doar daca llGetProducts = .T.
IF llGetProducts IF llGetProducts
LogMessage("[PRODUCTS] Starting product retrieval", "INFO", gcLogFile) LogMessage("[PRODUCTS] Starting product retrieval", "INFO", gcLogFile)
@@ -154,6 +154,15 @@ IF llGetProducts
IF !ISNULL(loJsonData) IF !ISNULL(loJsonData)
*-- Prima pagina - setam informatiile generale *-- Prima pagina - setam informatiile generale
IF lnCurrentPage = 1 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' IF TYPE('loJsonData.total') = 'C' OR TYPE('loJsonData.total') = 'N'
loAllJsonData.total = VAL(TRANSFORM(loJsonData.total)) loAllJsonData.total = VAL(TRANSFORM(loJsonData.total))
ENDIF ENDIF
@@ -164,8 +173,23 @@ IF llGetProducts
ENDIF ENDIF
*-- Adaugare produse din pagina curenta *-- Adaugare produse din pagina curenta
LOCAL llHasProducts, lnProductsFound
llHasProducts = .F.
lnProductsFound = 0
IF TYPE('loJsonData.products') = 'O' IF TYPE('loJsonData.products') = 'O'
*-- Numaram produsele din obiectul products
lnProductsFound = AMEMBERS(laProductsPage, loJsonData.products, 0)
IF lnProductsFound > 0
DO MergeProducts WITH loAllJsonData, loJsonData 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 ENDIF
*-- Verificare daca mai sunt pagini *-- Verificare daca mai sunt pagini
@@ -184,31 +208,39 @@ IF llGetProducts
lnCurrentPage = lnCurrentPage + 1 lnCurrentPage = lnCurrentPage + 1
ELSE ELSE
*-- Eroare parsare JSON *-- Salvare raspuns JSON raw in caz de eroare de parsare
LogMessage("[PRODUCTS] ERROR: JSON parsing failed for page " + TRANSFORM(lnCurrentPage), "ERROR", gcLogFile) lcFileName = "gomag_error_page" + TRANSFORM(lnCurrentPage) + "_" + DTOS(DATE()) + "_" + STRTRAN(TIME(), ":", "") + ".json"
STRTOFILE(lcResponse, lcFileName)
llHasMorePages = .F. llHasMorePages = .F.
ENDIF ENDIF
ELSE ELSE
*-- Eroare HTTP *-- Eroare HTTP - salvare in fisier de log
LogMessage("[PRODUCTS] HTTP Error " + TRANSFORM(lnStatusCode) + ": " + lcStatusText + " on page " + TRANSFORM(lnCurrentPage), "ERROR", gcLogFile) lcLogFileName = "gomag_error_page" + TRANSFORM(lnCurrentPage) + "_" + DTOS(DATE()) + "_" + STRTRAN(TIME(), ":", "") + ".log"
lcLogContent = "HTTP Error " + TRANSFORM(lnStatusCode) + ": " + lcStatusText + CHR(13) + CHR(10)
*-- Detalii despre eroare daca sunt disponibile *-- Incearca sa citesti raspunsul pentru detalii despre eroare
TRY TRY
lcErrorResponse = loHttp.ResponseText lcErrorResponse = loHttp.ResponseText
IF !EMPTY(lcErrorResponse) IF !EMPTY(lcErrorResponse)
LogMessage("[PRODUCTS] Error details: " + LEFT(lcErrorResponse, 200), "ERROR", gcLogFile) lcLogContent = lcLogContent + "Error Details:" + CHR(13) + CHR(10) + lcErrorResponse
ENDIF ENDIF
CATCH CATCH
LogMessage("[PRODUCTS] Could not read error details", "WARN", gcLogFile) lcLogContent = lcLogContent + "Could not read error details"
ENDTRY ENDTRY
STRTOFILE(lcLogContent, lcLogFileName)
llHasMorePages = .F. llHasMorePages = .F.
ENDIF ENDIF
CATCH TO loError CATCH TO loError
*-- Script error in products section *-- Salvare erori in fisier de log pentru pagina curenta
LogMessage("[PRODUCTS] Script Error #" + TRANSFORM(loError.ErrorNo) + ": " + loError.Message + " (Line: " + TRANSFORM(loError.LineNo) + ")", "ERROR", gcLogFile) 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. llHasMorePages = .F.
ENDTRY ENDTRY
@@ -226,18 +258,22 @@ ENDDO
LogMessage("[PRODUCTS] JSON saved: " + lcJsonFileName, "INFO", gcLogFile) LogMessage("[PRODUCTS] JSON saved: " + lcJsonFileName, "INFO", gcLogFile)
*-- Calculam numarul de produse procesate *-- Calculam numarul de produse procesate
IF TYPE('loAllJsonData.products') = 'O' IF TYPE('loAllJsonData.products') = 'O'
LOCAL ARRAY laProducts[1]
lnPropCount = AMEMBERS(laProducts, loAllJsonData.products, 0) lnPropCount = AMEMBERS(laProducts, loAllJsonData.products, 0)
gnProductsProcessed = lnPropCount gnProductsProcessed = lnPropCount
ENDIF ENDIF
ENDIF ENDIF
ELSE ELSE
LogMessage("[PRODUCTS] Skipped (disabled in settings)", "INFO", gcLogFile) LogMessage("[PRODUCTS] Skipped product retrieval (llGetProducts = .F.)", "INFO", gcLogFile)
ENDIF ENDIF
*-- SECTIUNEA COMENZI - se executa doar daca llGetOrders = .T. *-- SECTIUNEA COMENZI - se executa doar daca llGetOrders = .T.
IF llGetOrders IF llGetOrders
LogMessage("[ORDERS] Starting orders retrieval (last " + TRANSFORM(loSettings.OrderDaysBack) + " days from " + lcStartDateStr + ")", "INFO", gcLogFile) 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 *-- Reinitializare pentru comenzi
lnCurrentPage = 1 lnCurrentPage = 1
@@ -287,7 +323,8 @@ DO WHILE llHasMorePages
IF !ISNULL(loJsonData) IF !ISNULL(loJsonData)
*-- Debug: Afisam structura JSON pentru prima pagina *-- Debug: Afisam structura JSON pentru prima pagina
IF lnCurrentPage = 1 IF lnCurrentPage = 1
LogMessage("[ORDERS] DEBUG: Analyzing JSON structure...", "DEBUG", gcLogFile) LogMessage("[ORDERS] Analyzing JSON structure...", "INFO", gcLogFile)
LOCAL ARRAY laJsonProps[1]
lnPropCount = AMEMBERS(laJsonProps, loJsonData, 0) lnPropCount = AMEMBERS(laJsonProps, loJsonData, 0)
FOR lnDebugIndex = 1 TO MIN(lnPropCount, 10) && Primele 10 proprietati FOR lnDebugIndex = 1 TO MIN(lnPropCount, 10) && Primele 10 proprietati
lcPropName = laJsonProps(lnDebugIndex) lcPropName = laJsonProps(lnDebugIndex)
@@ -296,7 +333,7 @@ DO WHILE llHasMorePages
ENDFOR ENDFOR
ENDIF ENDIF
*-- Prima pagina - setam informatiile generale *-- Prima pagina - setam informatiile generale din metadata
IF lnCurrentPage = 1 IF lnCurrentPage = 1
IF TYPE('loJsonData.total') = 'C' OR TYPE('loJsonData.total') = 'N' IF TYPE('loJsonData.total') = 'C' OR TYPE('loJsonData.total') = 'N'
loAllOrderData.total = VAL(TRANSFORM(loJsonData.total)) loAllOrderData.total = VAL(TRANSFORM(loJsonData.total))
@@ -304,42 +341,26 @@ DO WHILE llHasMorePages
IF TYPE('loJsonData.pages') = 'C' OR TYPE('loJsonData.pages') = 'N' IF TYPE('loJsonData.pages') = 'C' OR TYPE('loJsonData.pages') = 'N'
loAllOrderData.pages = VAL(TRANSFORM(loJsonData.pages)) loAllOrderData.pages = VAL(TRANSFORM(loJsonData.pages))
ENDIF ENDIF
LogMessage("[ORDERS] Total items: " + TRANSFORM(loAllOrderData.total) + " | Pages: " + TRANSFORM(loAllOrderData.pages), "INFO", gcLogFile) LogMessage("[ORDERS] Total orders: " + TRANSFORM(loAllOrderData.total), "INFO", gcLogFile)
LogMessage("[ORDERS] Total pages: " + TRANSFORM(loAllOrderData.pages), "INFO", gcLogFile)
ENDIF ENDIF
*-- Adaugare comenzi din pagina curenta *-- Adaugare comenzi din pagina curenta
*-- API-ul GoMag returneaza un array direct de comenzi *-- API-ul GoMag returneaza obiect cu metadata si orders
LOCAL llHasOrders, lnOrdersFound LOCAL llHasOrders, lnOrdersFound
llHasOrders = .F. llHasOrders = .F.
lnOrdersFound = 0 lnOrdersFound = 0
*-- Verificam daca JSON-ul contine proprietatea orders *-- Verificam daca avem obiectul orders
IF TYPE('loJsonData.orders') = 'O' IF TYPE('loJsonData.orders') = 'O'
*-- Numaram comenzile din obiectul orders *-- Numaram comenzile din obiectul orders
LOCAL ARRAY laOrdersProps[1] lnOrdersFound = AMEMBERS(laOrdersPage, loJsonData.orders, 0)
LOCAL lnOrdersCount IF lnOrdersFound > 0
lnOrdersCount = AMEMBERS(laOrdersProps, loJsonData.orders, 0) *-- Mergem comenzile din pagina curenta
IF lnOrdersCount > 0
DO MergeOrdersArray WITH loAllOrderData, loJsonData DO MergeOrdersArray WITH loAllOrderData, loJsonData
llHasOrders = .T. llHasOrders = .T.
lnOrdersFound = lnOrdersCount LogMessage("[ORDERS] Found: " + TRANSFORM(lnOrdersFound) + " orders in page " + TRANSFORM(lnCurrentPage), "INFO", gcLogFile)
LogMessage("[ORDERS] Found " + TRANSFORM(lnOrdersCount) + " orders in page " + TRANSFORM(lnCurrentPage), "INFO", gcLogFile) gnOrdersProcessed = gnOrdersProcessed + lnOrdersFound
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
ENDIF ENDIF
@@ -347,15 +368,15 @@ DO WHILE llHasMorePages
LogMessage("[ORDERS] WARNING: No orders found in JSON response for page " + TRANSFORM(lnCurrentPage), "WARN", gcLogFile) LogMessage("[ORDERS] WARNING: No orders found in JSON response for page " + TRANSFORM(lnCurrentPage), "WARN", gcLogFile)
ENDIF ENDIF
*-- Verificare daca mai sunt pagini *-- Verificare daca mai sunt pagini folosind metadata
IF TYPE('loJsonData.pages') = 'C' OR TYPE('loJsonData.pages') = 'N' IF TYPE('loJsonData.pages') = 'C' OR TYPE('loJsonData.pages') = 'N'
lnTotalPages = VAL(TRANSFORM(loJsonData.pages)) lnTotalPages = VAL(TRANSFORM(loJsonData.pages))
IF lnCurrentPage >= lnTotalPages IF lnCurrentPage >= lnTotalPages
llHasMorePages = .F. llHasMorePages = .F.
ENDIF ENDIF
ELSE ELSE
*-- Daca nu avem info despre pagini, verificam daca sunt comenzi *-- Fallback: verifica daca am primit mai putin decat limita
IF !llHasOrders IF !llHasOrders OR lnOrdersFound < lnLimit
llHasMorePages = .F. llHasMorePages = .F.
ENDIF ENDIF
ENDIF ENDIF
@@ -363,31 +384,39 @@ DO WHILE llHasMorePages
lnCurrentPage = lnCurrentPage + 1 lnCurrentPage = lnCurrentPage + 1
ELSE ELSE
*-- Eroare parsare JSON pentru comenzi *-- Salvare raspuns JSON raw in caz de eroare de parsare
LogMessage("[ORDERS] ERROR: JSON parsing failed for page " + TRANSFORM(lnCurrentPage), "ERROR", gcLogFile) lcFileName = "gomag_order_error_page" + TRANSFORM(lnCurrentPage) + "_" + DTOS(DATE()) + "_" + STRTRAN(TIME(), ":", "") + ".json"
STRTOFILE(lcResponse, lcFileName)
llHasMorePages = .F. llHasMorePages = .F.
ENDIF ENDIF
ELSE ELSE
*-- Eroare HTTP pentru comenzi *-- Eroare HTTP - salvare in fisier de log
LogMessage("[ORDERS] HTTP Error " + TRANSFORM(lnStatusCode) + ": " + lcStatusText + " on page " + TRANSFORM(lnCurrentPage), "ERROR", gcLogFile) lcLogFileName = "gomag_order_error_page" + TRANSFORM(lnCurrentPage) + "_" + DTOS(DATE()) + "_" + STRTRAN(TIME(), ":", "") + ".log"
lcLogContent = "HTTP Error " + TRANSFORM(lnStatusCode) + ": " + lcStatusText + CHR(13) + CHR(10)
*-- Detalii despre eroare daca sunt disponibile *-- Incearca sa citesti raspunsul pentru detalii despre eroare
TRY TRY
lcErrorResponse = loHttp.ResponseText lcErrorResponse = loHttp.ResponseText
IF !EMPTY(lcErrorResponse) IF !EMPTY(lcErrorResponse)
LogMessage("[ORDERS] Error details: " + LEFT(lcErrorResponse, 200), "ERROR", gcLogFile) lcLogContent = lcLogContent + "Error Details:" + CHR(13) + CHR(10) + lcErrorResponse
ENDIF ENDIF
CATCH CATCH
LogMessage("[ORDERS] Could not read error details", "WARN", gcLogFile) lcLogContent = lcLogContent + "Could not read error details"
ENDTRY ENDTRY
STRTOFILE(lcLogContent, lcLogFileName)
llHasMorePages = .F. llHasMorePages = .F.
ENDIF ENDIF
CATCH TO loError CATCH TO loError
*-- Script error in orders section *-- Salvare erori in fisier de log pentru pagina curenta
LogMessage("[ORDERS] Script Error #" + TRANSFORM(loError.ErrorNo) + ": " + loError.Message + " (Line: " + TRANSFORM(loError.LineNo) + ")", "ERROR", gcLogFile) 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. llHasMorePages = .F.
ENDTRY ENDTRY
@@ -402,11 +431,11 @@ ENDDO
IF !ISNULL(loAllOrderData) AND TYPE('loAllOrderData.orders') = 'O' IF !ISNULL(loAllOrderData) AND TYPE('loAllOrderData.orders') = 'O'
lcOrderJsonFileName = lcOutputDir + "\gomag_orders_last7days_" + DTOS(DATE()) + "_" + STRTRAN(TIME(), ":", "") + ".json" lcOrderJsonFileName = lcOutputDir + "\gomag_orders_last7days_" + DTOS(DATE()) + "_" + STRTRAN(TIME(), ":", "") + ".json"
DO SaveOrdersArray WITH loAllOrderData, lcOrderJsonFileName DO SaveOrdersArray WITH loAllOrderData, lcOrderJsonFileName
LogMessage("[ORDERS] JSON saved: " + lcOrderJsonFileName, "INFO", gcLogFile) LogMessage("[ORDERS] JSON file created: " + lcOrderJsonFileName, "INFO", gcLogFile)
ENDIF ENDIF
ELSE ELSE
LogMessage("[ORDERS] Skipped (disabled in settings)", "INFO", gcLogFile) LogMessage("[ORDERS] Skipped order retrieval (llGetOrders = .F.)", "INFO", gcLogFile)
ENDIF ENDIF
*-- Curatare *-- Curatare

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

View File

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