Merge remote changes, keeping local versions of conflict files

This commit is contained in:
2025-09-09 19:27:02 +03:00
12 changed files with 1461 additions and 0 deletions

150
README-ORACLE-MODES.md Normal file
View File

@@ -0,0 +1,150 @@
# Oracle Modes Configuration Guide - UNIFIED
## 🎯 Un Singur Dockerfile + Docker Compose
| Oracle Version | Configurație .env | Comandă Build | Port |
|---------------|-------------------|---------------|------|
| 10g (test) | `INSTANTCLIENTPATH=...` | `docker-compose up --build` | 5003 |
| 11g (prod) | `INSTANTCLIENTPATH=...` | `docker-compose up --build` | 5003 |
| 12.1+ (nou) | `FORCE_THIN_MODE=true` | `ORACLE_MODE=thin docker-compose up --build` | 5003 |
---
## 🔧 THICK MODE (Oracle 10g/11g) - DEFAULT
### Configurare .env:
```env
# Uncomment această linie pentru thick mode:
INSTANTCLIENTPATH=/opt/oracle/instantclient_23_9
# Comment această linie:
# FORCE_THIN_MODE=true
```
### Rulare:
```bash
docker-compose up --build -d
curl http://localhost:5003/health
```
---
## 🚀 THIN MODE (Oracle 12.1+)
### Varianta 1 - Prin .env (Recomandat):
```env
# Comment această linie pentru thin mode:
# INSTANTCLIENTPATH=/opt/oracle/instantclient_23_9
# Uncomment această linie:
FORCE_THIN_MODE=true
```
### Varianta 2 - Prin build argument:
```bash
ORACLE_MODE=thin docker-compose up --build -d
```
### Test:
```bash
curl http://localhost:5003/health
```
---
## 🔄 LOGICA AUTO-DETECT
Container-ul detectează automat modul:
1. **FORCE_THIN_MODE=true****Thin Mode**
2. **INSTANTCLIENTPATH** există → **Thick Mode**
3. Build cu **ORACLE_MODE=thin****Thin Mode**
4. Default → **Thick Mode**
---
## 🛠️ COMENZI SIMPLE
### Pentru Oracle 10g/11g (setup-ul tău actual):
```bash
# Verifică .env să aibă:
grep INSTANTCLIENTPATH ./api/.env
# Start
docker-compose up --build -d
curl http://localhost:5003/test-db
```
### Pentru Oracle 12.1+ (viitor):
```bash
# Editează .env: decomentează FORCE_THIN_MODE=true
# SAU rulează direct:
ORACLE_MODE=thin docker-compose up --build -d
curl http://localhost:5003/test-db
```
### Switch rapid:
```bash
# Stop
docker-compose down
# Edit .env (change INSTANTCLIENTPATH ↔ FORCE_THIN_MODE)
# Start
docker-compose up --build -d
```
---
## ⚠️ TROUBLESHOOTING
### Eroare DPY-3010 în Thin Mode:
```
DPY-3010: connections to this database server version are not supported
```
**Soluție:** Oracle este 11g sau mai vechi → folosește thick mode
### Eroare libaio în Thick Mode:
```
Cannot locate a 64-bit Oracle Client library: libaio.so.1
```
**Soluție:** Rebuild container (fix automat în Dockerfile.thick)
### Container nu pornește:
```bash
docker-compose logs
docker-compose down && docker-compose up --build
```
---
## 📊 COMPARAȚIE PERFORMANȚĂ
| Aspect | Thick Mode | Thin Mode |
|--------|------------|-----------|
| Container Size | ~200MB | ~50MB |
| Startup Time | 10-15s | 3-5s |
| Memory Usage | ~100MB | ~30MB |
| Oracle Support | 10g+ | 12.1+ |
| Dependencies | Instant Client | None |
---
## 🔧 DEZVOLTARE
### Pentru dezvoltatori:
1. **Thick mode** pentru compatibilitate maximă
2. **Thin mode** pentru development rapid pe Oracle nou
3. **Auto-detect** în producție pentru flexibilitate
### Testare ambele moduri:
```bash
# Thick pe port 5003
docker-compose -f docker-compose.thick.yaml up -d
# Thin pe port 5004
docker-compose -f docker-compose.thin.yaml up -d
# Test ambele
curl http://localhost:5003/health
curl http://localhost:5004/health
```

15
api/.env.example Normal file
View File

@@ -0,0 +1,15 @@
# Oracle Database Configuration
ORACLE_USER=YOUR_ORACLE_USERNAME
ORACLE_PASSWORD=YOUR_ORACLE_PASSWORD
ORACLE_DSN=YOUR_TNS_CONNECTION_NAME
TNS_ADMIN=/app
INSTANTCLIENTPATH=/opt/oracle/instantclient_21_1
# Flask Configuration
FLASK_ENV=development
FLASK_DEBUG=1
PYTHONUNBUFFERED=1
# Application Settings
APP_PORT=5000
LOG_LEVEL=DEBUG

43
api/01_create_table.sql Normal file
View File

@@ -0,0 +1,43 @@
-- ====================================================================
-- P1-001: Tabel ARTICOLE_TERTI pentru mapări SKU → CODMAT
-- Sistem Import Comenzi Web → ROA
-- ====================================================================
-- Creare tabel pentru mapări complexe articole
CREATE TABLE ARTICOLE_TERTI (
sku VARCHAR2(100) NOT NULL, -- SKU din platforma web
codmat VARCHAR2(50) NOT NULL, -- CODMAT din nom_articole
cantitate_roa NUMBER(10,3) DEFAULT 1, -- Câte unități ROA = 1 web
procent_pret NUMBER(5,2) DEFAULT 100, -- % din preț pentru seturi
activ NUMBER(1) DEFAULT 1, -- 1=activ, 0=inactiv
data_creare DATE DEFAULT SYSDATE, -- Timestamp creare
data_modif DATE DEFAULT SYSDATE, -- Timestamp ultima modificare
id_util_creare NUMBER(10) DEFAULT -3, -- ID utilizator care a creat
id_util_modif NUMBER(10) DEFAULT -3 -- ID utilizator care a modificat
);
-- Adaugare constraint-uri ca instructiuni separate
ALTER TABLE ARTICOLE_TERTI ADD CONSTRAINT pk_articole_terti PRIMARY KEY (sku, codmat);
ALTER TABLE ARTICOLE_TERTI ADD CONSTRAINT chk_art_terti_cantitate CHECK (cantitate_roa > 0);
ALTER TABLE ARTICOLE_TERTI ADD CONSTRAINT chk_art_terti_procent CHECK (procent_pret >= 0 AND procent_pret <= 100);
ALTER TABLE ARTICOLE_TERTI ADD CONSTRAINT chk_art_terti_activ CHECK (activ IN (0, 1));
-- Index pentru performanță pe căutări frecvente după SKU
CREATE INDEX idx_articole_terti_sku ON ARTICOLE_TERTI (sku, activ);
-- Comentarii pentru documentație
COMMENT ON TABLE ARTICOLE_TERTI IS 'Mapări SKU-uri web → CODMAT ROA pentru reîmpachetări și seturi';
COMMENT ON COLUMN ARTICOLE_TERTI.sku IS 'SKU din platforma web (ex: GoMag)';
COMMENT ON COLUMN ARTICOLE_TERTI.codmat IS 'CODMAT din nom_articole ROA';
COMMENT ON COLUMN ARTICOLE_TERTI.cantitate_roa IS 'Câte unități ROA pentru 1 unitate web';
COMMENT ON COLUMN ARTICOLE_TERTI.procent_pret IS 'Procent din preț web alocat acestui CODMAT (pentru seturi)';
COMMENT ON COLUMN ARTICOLE_TERTI.activ IS '1=mapare activă, 0=dezactivată';
-- Date de test pentru validare
INSERT INTO ARTICOLE_TERTI (sku, codmat, cantitate_roa, procent_pret, activ) VALUES ('CAFE100', 'CAF01', 10, 100, 1);
INSERT INTO ARTICOLE_TERTI (sku, codmat, cantitate_roa, procent_pret, activ) VALUES ('SET01', 'CAF01', 2, 60, 1);
INSERT INTO ARTICOLE_TERTI (sku, codmat, cantitate_roa, procent_pret, activ) VALUES ('SET01', 'FILT01', 1, 40, 1);

38
api/Dockerfile Normal file
View File

@@ -0,0 +1,38 @@
# UNIFIED Dockerfile - AUTO-DETECT Thick/Thin Mode
FROM python:3.11-slim as base
# Set argument for build mode (thick by default for compatibility)
ARG ORACLE_MODE=thick
# Base application setup
WORKDIR /app
COPY requirements.txt /app/requirements.txt
RUN pip3 install -r requirements.txt
# Oracle Instant Client installation (only if thick mode)
RUN if [ "$ORACLE_MODE" = "thick" ] ; then \
apt-get update && apt-get install -y libaio-dev wget unzip curl && \
mkdir -p /opt/oracle && cd /opt/oracle && \
wget https://download.oracle.com/otn_software/linux/instantclient/instantclient-basiclite-linuxx64.zip && \
unzip instantclient-basiclite-linuxx64.zip && \
rm -f instantclient-basiclite-linuxx64.zip && \
cd /opt/oracle/instantclient* && \
rm -f *jdbc* *occi* *mysql* *README *jar uidrvci genezi adrci && \
echo /opt/oracle/instantclient* > /etc/ld.so.conf.d/oracle-instantclient.conf && \
ldconfig && \
ln -sf /usr/lib/x86_64-linux-gnu/libaio.so.1t64 /usr/lib/x86_64-linux-gnu/libaio.so.1 ; \
else \
echo "Thin mode - skipping Oracle Instant Client installation" ; \
fi
# Copy application files
COPY . .
# Create logs directory
RUN mkdir -p /app/logs
# Expose port
EXPOSE 5000
# Run Flask application with auto-detect mode
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "admin:app", "--reload", "--access-logfile", "-"]

250
api/admin.py Normal file
View File

@@ -0,0 +1,250 @@
"""
Flask Admin Interface pentru Import Comenzi Web → ROA
Gestionează mapările SKU în tabelul ARTICOLE_TERTI
"""
from flask import Flask, jsonify, request, render_template_string
from flask_cors import CORS
from dotenv import load_dotenv
import oracledb
import os
import logging
from datetime import datetime
# Configurare environment
load_dotenv()
# Configurare logging
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s | %(levelname)s | %(message)s',
handlers=[
logging.FileHandler('/app/logs/admin.log'),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
# Environment Variables pentru Oracle
user = os.environ['ORACLE_USER']
password = os.environ['ORACLE_PASSWORD']
dsn = os.environ['ORACLE_DSN']
# Oracle client - AUTO-DETECT: thick mode pentru 10g/11g, thin mode pentru 12.1+
force_thin_mode = os.environ.get('FORCE_THIN_MODE', 'false').lower() == 'true'
instantclient_path = os.environ.get('INSTANTCLIENTPATH')
if force_thin_mode:
logger.info(f"FORCE_THIN_MODE=true: Folosind thin mode pentru {dsn} (Oracle 12.1+ required)")
elif instantclient_path:
try:
oracledb.init_oracle_client(lib_dir=instantclient_path)
logger.info(f"Thick mode activat pentru {dsn} (compatibil Oracle 10g/11g/12.1+)")
except Exception as e:
logger.error(f"Eroare thick mode: {e}")
logger.info("Fallback la thin mode - verifică că Oracle DB este 12.1+")
else:
logger.info(f"Thin mode (default) pentru {dsn} - Oracle 12.1+ required")
app = Flask(__name__)
CORS(app)
def start_pool():
"""Inițializează connection pool Oracle"""
try:
pool = oracledb.create_pool(
user=user,
password=password,
dsn=dsn,
min=2,
max=4,
increment=1
)
logger.info(f"Oracle pool creat cu succes pentru {dsn}")
return pool
except Exception as e:
logger.error(f"Eroare creare pool Oracle: {e}")
raise
@app.route('/health')
def health():
"""Health check pentru Docker"""
return jsonify({"status": "ok", "timestamp": datetime.now().isoformat()})
@app.route('/')
def home():
"""Pagina principală admin interface"""
html_template = """
<!DOCTYPE html>
<html>
<head>
<title>GoMag Admin - Mapări SKU</title>
<meta charset="utf-8">
<style>
body { font-family: Arial, sans-serif; margin: 40px; background-color: #f5f5f5; }
.container { max-width: 1200px; margin: 0 auto; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
h1 { color: #333; border-bottom: 3px solid #007bff; padding-bottom: 10px; }
.status { padding: 10px; border-radius: 4px; margin: 10px 0; }
.success { background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
.error { background-color: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
.btn { background: #007bff; color: white; padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; margin: 5px; }
.btn:hover { background: #0056b3; }
.table-container { margin-top: 20px; }
table { width: 100%; border-collapse: collapse; margin-top: 10px; }
th, td { padding: 8px 12px; text-align: left; border-bottom: 1px solid #ddd; }
th { background-color: #f8f9fa; font-weight: bold; }
tr:hover { background-color: #f5f5f5; }
</style>
</head>
<body>
<div class="container">
<h1>🛍️ GoMag Admin - Import Comenzi Web → ROA</h1>
<div id="status-area">
<div class="success">✅ Container Docker activ pe port 5003</div>
<div id="db-status">🔄 Verificare conexiune Oracle...</div>
</div>
<div class="table-container">
<h2>📋 Mapări SKU Active</h2>
<button class="btn" onclick="loadMappings()">🔄 Reîmprospătează</button>
<button class="btn" onclick="testConnection()">🔍 Test Conexiune DB</button>
<div id="mappings-container">
<p>Loading...</p>
</div>
</div>
</div>
<script>
// Test conexiune la load
window.onload = function() {
testConnection();
loadMappings();
}
function testConnection() {
fetch('/test-db')
.then(response => response.json())
.then(data => {
const statusDiv = document.getElementById('db-status');
if (data.success) {
statusDiv.className = 'status success';
statusDiv.innerHTML = '✅ Oracle conectat: ' + data.message;
} else {
statusDiv.className = 'status error';
statusDiv.innerHTML = '❌ Eroare Oracle: ' + data.error;
}
})
.catch(error => {
document.getElementById('db-status').innerHTML = '❌ Eroare fetch: ' + error;
});
}
function loadMappings() {
fetch('/api/mappings')
.then(response => response.json())
.then(data => {
let html = '<table>';
html += '<tr><th>SKU</th><th>CODMAT</th><th>Cantitate ROA</th><th>Procent Preț</th><th>Activ</th><th>Data Creare</th></tr>';
if (data.mappings && data.mappings.length > 0) {
data.mappings.forEach(row => {
const activIcon = row[4] === 1 ? '' : '';
html += `<tr>
<td><strong>${row[0]}</strong></td>
<td>${row[1]}</td>
<td>${row[2]}</td>
<td>${row[3]}%</td>
<td>${activIcon}</td>
<td>${new Date(row[5]).toLocaleDateString()}</td>
</tr>`;
});
} else {
html += '<tr><td colspan="6">Nu există mapări configurate</td></tr>';
}
html += '</table>';
document.getElementById('mappings-container').innerHTML = html;
})
.catch(error => {
document.getElementById('mappings-container').innerHTML = '❌ Eroare: ' + error;
});
}
</script>
</body>
</html>
"""
return render_template_string(html_template)
@app.route('/test-db')
def test_db():
"""Test conexiune Oracle și verificare tabel"""
try:
with pool.acquire() as con:
with con.cursor() as cur:
# Test conexiune de bază
cur.execute("SELECT SYSDATE FROM DUAL")
db_date = cur.fetchone()[0]
# Verificare existență tabel ARTICOLE_TERTI
cur.execute("""
SELECT COUNT(*) FROM USER_TABLES
WHERE TABLE_NAME = 'ARTICOLE_TERTI'
""")
table_exists = cur.fetchone()[0] > 0
if not table_exists:
return jsonify({
"success": False,
"error": "Tabelul ARTICOLE_TERTI nu există. Rulează 01_create_table.sql"
})
# Count records
cur.execute("SELECT COUNT(*) FROM ARTICOLE_TERTI")
record_count = cur.fetchone()[0]
return jsonify({
"success": True,
"message": f"DB Time: {db_date}, Records: {record_count}",
"table_exists": table_exists,
"record_count": record_count
})
except Exception as e:
logger.error(f"Test DB failed: {e}")
return jsonify({"success": False, "error": str(e)})
@app.route('/api/mappings')
def get_mappings():
"""Returnează toate mapările SKU active"""
try:
with pool.acquire() as con:
with con.cursor() as cur:
cur.execute("""
SELECT sku, codmat, cantitate_roa, procent_pret, activ, data_creare
FROM ARTICOLE_TERTI
ORDER BY sku, codmat
""")
mappings = cur.fetchall()
return jsonify({
"success": True,
"mappings": mappings,
"count": len(mappings)
})
except Exception as e:
logger.error(f"Get mappings failed: {e}")
return jsonify({"success": False, "error": str(e)})
# Inițializare pool la startup
try:
pool = start_pool()
logger.info("Admin interface started successfully")
except Exception as e:
logger.error(f"Failed to start admin interface: {e}")
pool = None
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)

5
api/requirements.txt Normal file
View File

@@ -0,0 +1,5 @@
Flask==2.3.2
Flask-CORS==4.0.0
oracledb==1.4.2
python-dotenv==1.0.0
gunicorn==21.2.0

9
api/tnsnames.ora Normal file
View File

@@ -0,0 +1,9 @@
ROA_CENTRAL =
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST = 10.0.20.122)(PORT = 1521))
)
(CONNECT_DATA =
(SID = ROA)
)
)

37
docker-compose.yaml Normal file
View File

@@ -0,0 +1,37 @@
# UNIFIED Docker Compose - AUTO-DETECT Oracle Mode
#
# Configurare prin .env:
# - Oracle 10g/11g: setează INSTANTCLIENTPATH=/opt/oracle/instantclient_23_9
# - Oracle 12.1+: setează FORCE_THIN_MODE=true (sau elimină INSTANTCLIENTPATH)
#
# Build modes:
# - docker-compose up --build → thick mode (default)
# - docker-compose up --build --build-arg ORACLE_MODE=thin → thin mode
services:
gomag_admin:
build:
context: ./api
dockerfile: Dockerfile
args:
# thick = Oracle 10g/11g/12.1+ (cu Instant Client)
# thin = Oracle 12.1+ only (fără Instant Client)
ORACLE_MODE: ${ORACLE_MODE:-thick}
container_name: gomag-admin
ports:
- "5003:5000"
volumes:
- ./api:/app
- ./logs:/app/logs
env_file:
- ./api/.env
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
interval: 30s
timeout: 10s
retries: 3
networks:
default:
driver: bridge

View File

@@ -0,0 +1,222 @@
# LLM Project Manager Prompt
## Pentru Implementarea PRD: Import Comenzi Web → Sistem ROA
Tu ești un **Project Manager AI specializat** care urmărește implementarea unui PRD (Product Requirements Document) prin descompunerea în user stories executabile și urmărirea progresului.
---
## 🎯 Misiunea Ta
Implementezi sistemul de import automat comenzi web → ERP ROA Oracle conform PRD-ului furnizat. Vei coordona dezvoltarea în 4 faze distincte, urmărind fiecare story și asigurându-te că totul este livrat conform specificațiilor.
---
## 📋 Context PRD
**Sistem:** Import comenzi de pe platforme web (GoMag, etc.) în sistemul ERP ROA Oracle
**Tech Stack:** Oracle PL/SQL + Visual FoxPro 9 + FastApi (admin interface)
**Componente Principale:**
- Package Oracle pentru parteneri și comenzi
- Orchestrator VFP pentru sincronizare automată
- Interfață web pentru administrare mapări SKU
- Tabel nou ARTICOLE_TERTI pentru mapări complexe
---
## 📊 User Stories Framework
Pentru fiecare story, vei genera:
### Story Template:
```
**Story ID:** [FASE]-[NR] (ex: P1-001)
**Titlu:** [Descriere concisă]
**As a:** [Utilizator/Sistem]
**I want:** [Funcționalitate dorită]
**So that:** [Beneficiul de business]
**Acceptance Criteria:**
- [ ] Criteriu 1
- [ ] Criteriu 2
- [ ] Criteriu 3
**Technical Tasks:**
- [ ] Task tehnic 1
- [ ] Task tehnic 2
**Definition of Done:**
- [ ] Cod implementat și testat
- [ ] Documentație actualizată
- [ ] Error handling complet
- [ ] Logging implementat
- [ ] Review code efectuat
**Estimate:** [XS/S/M/L/XL] ([ore estimate])
**Dependencies:** [Alte story-uri necesare]
**Risk Level:** [Low/Medium/High]
```
---
## 🏗️ Faze de Implementare
### **PHASE 1: Database Foundation (Ziua 1)**
Creează story-uri pentru:
- Tabel ARTICOLE_TERTI cu structura specificată
- Package IMPORT_PARTENERI complet funcțional
- Package IMPORT_COMENZI cu logica de mapare
- Teste unitare pentru package-uri
### **PHASE 2: VFP Integration (Ziua 2)**
Creează story-uri pentru:
- Adaptare gomag-vending-test.prg pentru JSON output
- Orchestrator sync-comenzi-web.prg cu timer
- Integrare Oracle packages în VFP
- Sistem de logging cu rotație
### **PHASE 3: Web Admin Interface (Ziua 3)**
Creează story-uri pentru:
- Flask app cu Oracle connection pool
- HTML/CSS interface pentru admin mapări
- JavaScript pentru CRUD operații
- Validări client-side și server-side
### **PHASE 4: Testing & Deployment (Ziua 4)**
Creează story-uri pentru:
- Testare end-to-end cu comenzi reale
- Validare mapări complexe (seturi, reîmpachetări)
- Configurare environment production
- Documentație utilizare finală
---
## 🔄 Workflow de Urmărire
### La început de sesiune:
1. **Prezintă status overview:** "PHASE X - Y% complete, Z stories remaining"
2. **Identifică story-ul curent** și dependencies
3. **Verifică blocaje** și propune soluții
4. **Actualizează planning-ul** dacă e nevoie
### Pe durata implementării:
1. **Urmărește progresul** fiecărui task în story
2. **Validează completion criteria** înainte să marchezi DONE
3. **Identifică riscos** și alertează proactiv
4. **Propune optimizări** de proces
### La finalizare story:
1. **Demo功能** implementată
2. **Confirmă acceptance criteria** îndeplinite
3. **Planifică next story** cu dependencies
4. **Actualizează overall progress**
---
## 📊 Tracking & Reporting
### Daily Status Format:
```
📈 PROJECT STATUS - [DATA]
═══════════════════════════════════
🎯 Current Phase: [PHASE X]
📊 Overall Progress: [X]% ([Y]/[Z] stories done)
⏰ Current Story: [STORY-ID] - [TITLE]
🔄 Status: [IN PROGRESS/BLOCKED/READY FOR REVIEW]
📋 Today's Completed:
- ✅ [Story completă]
- ✅ [Task complet]
🚧 In Progress:
- 🔄 [Story în lucru]
- ⏳ [Task în progress]
⚠️ Blockers:
- 🚨 [Blocker 1]
- 🔍 [Issue necesitând decizie]
📅 Next Up:
- 📝 [Next story ready]
- 🔜 [Upcoming dependency]
🎯 Phase Target: [Data target] | Risk: [LOW/MED/HIGH]
```
### Weekly Sprint Review:
- Retrospectivă story-uri complete vs planificate
- Analiza blockers întâlniți și soluții
- Ajustări planning pentru săptămâna următoare
- Identificare lesson learned
---
## 🚨 Risk Management
### Categorii Risc:
- **HIGH:** Blockers care afectează multiple story-uri
- **MEDIUM:** Delay-uri care pot afecta phase target
- **LOW:** Issues locale care nu afectează planning-ul
### Escalation Matrix:
1. **Technical Issues:** Propui soluții alternative/workaround
2. **Dependency Blockers:** Replanifici priority și sequence
3. **Scope Changes:** Alertezi și ceri validare înainte de implementare
---
## 🎛️ Comenzi Disponibile
Răspunzi la comenzile:
- `status` - Overall progress și current story
- `stories` - Lista toate story-urile cu status
- `phase` - Detalii phase curentă
- `risks` - Identifică și prioritizează riscuri
- `demo [story-id]` - Demonstrație funcționalitate implementată
- `plan` - Re-planificare dacă apar schimbări
---
## 💡 Success Criteria
### Technical KPIs:
- Import success rate > 95%
- Timp mediu procesare < 30s per comandă
- Zero downtime pentru ROA principal
- 100% log coverage
### Project KPIs:
- Stories delivered on time: >90%
- Zero blockers mai mult de 1 zi
- Code review coverage: 100%
- Documentation completeness: 100%
---
## 🤖 Personality & Communication Style
- **Proactiv:** Anticipezi probleme și propui soluții
- **Data-driven:** Folosești metrici concrete pentru tracking
- **Pragmatic:** Focusat pe delivery și rezultate practice
- **Comunicativ:** Updates clare și acționabile
- **Quality-focused:** Nu accepti compromisuri pe Definition of Done
---
## 🚀 Getting Started
**Primul tau task:**
1. Citește întregul PRD furnizat
2. Generează toate story-urile pentru Phase 1
3. Prezintă overall project plan cu timeline
4. Începe tracking primul story
**Întreabă-mă dacă:**
- Necesită clarificări tehnice despre PRD
- Vrei să ajustez priority sau sequence
- Apare vreo dependency neidentificată
- Ai nevoie de input pentru estimări
---
**Acum începe cu:** "Am analizat PRD-ul și sunt gata să coordonez implementarea. Să încep cu generarea story-urilor pentru Phase 1?"

319
docs/PRD.md Normal file
View File

@@ -0,0 +1,319 @@
# Product Requirements Document (PRD)
## Import Comenzi Web → Sistem ROA
**Versiune:** 1.1
**Data:** 08 septembrie 2025
**Status:** Phase 1 - în progres (P1-001 ✅ complet)
---
## 📋 Overview
Sistem ultra-minimal pentru importul comenzilor de pe platforme web (GoMag, etc.) în sistemul ERP ROA Oracle. Sistemul gestionează automat maparea produselor, crearea clienților și generarea comenzilor în ROA.
### Obiective Principale
- ✅ Import automat comenzi web → ROA
- ✅ Mapare flexibilă SKU → CODMAT (reîmpachetări + seturi)
- ✅ Crearea automată a partenerilor noi
- ✅ Interfață web pentru administrare mapări
- ✅ Logging complet pentru troubleshooting
---
## 🎯 Scope & Limitations
### În Scope
- Import comenzi din orice platformă web (nu doar GoMag)
- Mapare SKU complexe (1:1, 1:N, reîmpachetări, seturi)
- Crearea automată parteneri + adrese
- Interfață web admin pentru mapări
- Logging în fișiere text
### Out of Scope
- Modificarea comenzilor existente în ROA
- Sincronizare bidirectională
- Gestionarea stocurilor
- Interfață pentru utilizatori finali
---
## 🏗️ Architecture Overview
```
[Web Platform API] → [VFP Orchestrator] → [Oracle PL/SQL] → [Web Admin Interface]
↓ ↓ ↑ ↑
JSON Orders Process & Log Store/Update Configuration
```
### Tech Stack
- **Backend:** Oracle PL/SQL packages
- **Integration:** Visual FoxPro 9
- **Admin Interface:** Flask + Oracle
- **Data:** Oracle 11g/12c
---
## 📊 Data Model
### Tabel Nou: ARTICOLE_TERTI
```sql
CREATE TABLE ARTICOLE_TERTI (
sku VARCHAR2(100), -- SKU din platforma web
codmat VARCHAR2(50), -- CODMAT din nom_articole
cantitate_roa NUMBER(10,3), -- Câte unități ROA = 1 web
procent_pret NUMBER(5,2), -- % din preț pentru seturi
activ NUMBER(1), -- 1=activ, 0=inactiv
PRIMARY KEY (sku, codmat)
);
```
### Exemple Mapări
- **Simplu:** SKU "CAF01" → caută direct în nom_articole (nu se stochează)
- **Reîmpachetare:** SKU "CAFE100" → CODMAT "CAF01", cantitate_roa=10
- **Set compus:**
- SKU "SET01" → CODMAT "CAF01", cantitate_roa=2, procent_pret=60
- SKU "SET01" → CODMAT "FILT01", cantitate_roa=1, procent_pret=40
---
## 🔧 Components Specification
### 1. Package IMPORT_PARTENERI
**Funcții:**
- `cauta_sau_creeaza_partener()` - Găsește partener existent sau creează unul nou
- `parseaza_adresa_semicolon()` - Parsează adrese format: "JUD:București;BUCURESTI;Str.Victoriei;10"
**Logica Căutare Parteneri:**
1. Caută după cod_fiscal (dacă > 3 caractere)
2. Caută după denumire exactă
3. Creează partener nou folosind `pack_def.adauga_partener()`
4. Adaugă adresa folosind `pack_def.adauga_adresa_partener2()`
### 2. Package IMPORT_COMENZI
**Funcții:**
- `gaseste_articol_roa()` - Rezolvă SKU → articole ROA
- `importa_comanda_web()` - Import comandă completă
**Logica Articole:**
1. Verifică ARTICOLE_TERTI pentru SKU
2. Dacă nu există → caută direct în nom_articole (SKU = CODMAT)
3. Calculează cantități și prețuri conform mapărilor
4. Folosește `PACK_COMENZI.adauga_comanda()` și `PACK_COMENZI.adauga_articol_comanda()`
### 3. VFP Orchestrator (sync-comenzi-web.prg)
**Responsabilități:**
- Rulare automată (timer 5 minute)
- Citire comenzi din API-ul web
- Apelare package-uri Oracle
- Logging în fișiere text cu timestamp
### 4. Web Admin Interface
**Funcționalități:**
- Vizualizare mapări SKU existente
- Adăugare/editare/ștergere mapări
- Validare date înainte de salvare
- Interface responsive cu Flask
---
## 📋 Implementation Phases
### Phase 1: Database Foundation (Ziua 1) - 🔄 În Progres
- [x]**P1-001:** Creare tabel ARTICOLE_TERTI + Docker setup
- [ ] 🔄 **P1-002:** Package IMPORT_PARTENERI complet
- [ ]**P1-003:** Package IMPORT_COMENZI complet
- [ ]**P1-004:** Testare manuală package-uri
### Phase 2: VFP Integration (Ziua 2)
- [ ] Adaptare gomag-vending-test.prg pentru output JSON
- [ ] Creare sync-comenzi-web.prg
- [ ] Testare import comenzi end-to-end
- [ ] Configurare logging
### Phase 3: Web Admin Interface (Ziua 3)
- [ ] Flask app cu connection pool Oracle
- [ ] HTML/CSS pentru admin mapări
- [ ] JavaScript pentru CRUD operații
- [ ] Testare interfață web
### Phase 4: Testing & Deployment (Ziua 4)
- [ ] Testare integrată pe comenzi reale
- [ ] Validare mapări complexe (seturi)
- [ ] Configurare environment production
- [ ] Documentație utilizare
---
## 📁 File Structure
```
/api/ # ✅ Flask Admin Interface
├── admin.py # ✅ Flask app cu Oracle pool
├── 01_create_table.sql # ✅ Tabel ARTICOLE_TERTI
├── 02_import_parteneri.sql # 🔄 Package parteneri (în progres)
├── 03_import_comenzi.sql # ⏳ Package comenzi (planificat)
├── Dockerfile # ✅ Container cu Oracle client
├── tnsnames.ora # ✅ Config Oracle ROA
├── .env # ✅ Environment variables
└── requirements.txt # ✅ Dependencies Python
/vfp/ # ⏳ VFP Integration (Phase 2)
└── sync-comenzi-web.prg # ⏳ Orchestrator principal
/docker-compose.yaml # ✅ Container orchestration
/logs/ # ✅ Logging directory
```
---
## 🔒 Business Rules
### Parteneri
- Căutare prioritate: cod_fiscal → denumire → creare nou
- Persoane fizice (CUI 13 cifre): separă nume/prenume
- Adrese: defaultează la București Sectorul 1 dacă nu găsește
- Toate partenerele noi au ID_UTIL = -3 (sistem)
### Articole
- SKU simple (găsite direct în nom_articole): nu se stochează în ARTICOLE_TERTI
- Mapări speciale: doar reîmpachetări și seturi complexe
- Validare: suma procent_pret pentru același SKU să fie logic
- Articole inactive: activ=0 (nu se șterg)
### Comenzi
- Folosește package-urile existente (PACK_COMENZI)
- ID_GESTIUNE = 1, ID_SECTIE = 1, ID_POL = 0 (default)
- Data livrare = data comenzii + 1 zi
- Toate comenzile au INTERNA = 0 (externe)
---
## 📊 Success Metrics
### Technical Metrics
- Import success rate > 95%
- Timpul mediu de procesare < 30s per comandă
- Zero downtime pentru sistemul principal ROA
- Log coverage 100% (toate operațiile logate)
### Business Metrics
- Reducerea timpului de introducere comenzi cu 90%
- Eliminarea erorilor manuale de transcriere
- Timpul de configurare mapări noi < 5 minute
---
## 🚨 Error Handling
### Categorii Erori
1. **Erori conexiune Oracle:** Retry logic + alertă
2. **SKU not found:** Log warning + skip articol
3. **Partener invalid:** Tentativă creare + log detalii
4. **Comenzi duplicate:** Skip cu log info
### Logging Format
```
2025-09-08 14:30:25 | COMANDA-123 | OK | ID:456789
2025-09-08 14:30:26 | COMANDA-124 | ERROR | SKU 'XYZ' not found
```
---
## 🔧 Configuration
### Environment Variables (.env)
```env
ORACLE_USER=CONTAFIN_ORACLE
ORACLE_PASSWORD=********
ORACLE_DSN=ROA_ROMFAST
TNS_ADMIN=/app
INSTANTCLIENTPATH=/opt/oracle/instantclient
```
### VFP Configuration
- Timer interval: 300 secunde (5 minute)
- Conexiune Oracle prin goExecutor existent
- Log files: sync_YYYYMMDD.log (rotație zilnică)
---
## 🎛️ Admin Interface Specification
### Main Screen: SKU Mappings
- Tabel editabil cu coloane: SKU, CODMAT, Cantitate ROA, Procent Preț, Activ
- Inline editing cu auto-save
- Filtrare și căutare
- Export/Import mapări (CSV)
- Validare în timp real
### Features
- Bulk operations (activare/dezactivare multiple)
- Template mapări pentru tipuri comune
- Preview calcul preț pentru teste
- Audit trail (cine/când a modificat)
---
## 🏁 Definition of Done
### Per Feature
- [ ] Cod implementat și testat
- [ ] Documentație actualizată
- [ ] Error handling complet
- [ ] Logging implementat
- [ ] Review code efectuat
### Per Phase
- [ ] Toate feature-urile Phase complete
- [ ] Testare integrată reușită
- [ ] Performance requirements îndeplinite
- [ ] Deployment verificat
- [ ] Sign-off stakeholder
---
## 📞 Support & Maintenance
### Monitoring
- Log files în /logs/ cu rotație automată
- Alertă email pentru erori critice
- Dashboard cu statistici import (opcional Phase 2)
### Backup & Recovery
- Mapări ARTICOLE_TERTI incluse în backup-ul zilnic ROA
- Config files versionate în Git
- Procedură rollback pentru package-uri Oracle
---
---
## 📊 Progress Status - Phase 1
### ✅ P1-001 COMPLET: Tabel ARTICOLE_TERTI
- **Implementat:** 08 septembrie 2025, 22:30
- **Deliverables:**
- Tabel ARTICOLE_TERTI cu structură completă (PK, validări, indecși)
- Docker environment cu Oracle Instant Client
- Flask admin interface cu test conexiune
- Date test pentru mapări (reîmpachetare + set compus)
- **Files:** `api/01_create_table.sql`, `api/admin.py`, `docker-compose.yaml`
- **Status:** Ready pentru testare cu ROA (10.0.20.36)
### 🔄 Următorul: P1-002 Package IMPORT_PARTENERI
- **Funcții de implementat:**
- `cauta_sau_creeaza_partener()`
- `parseaza_adresa_semicolon()`
- **Dependencies:** P1-001 complet
- **Estimate:** 6-8 ore
- **Risk:** MEDIUM (integrare cu pack_def existent)
---
**Document Owner:** Development Team
**Last Updated:** 08 septembrie 2025, 22:35
**Next Review:** După P1-002 completion

373
gomag-vending-test.prg Normal file
View File

@@ -0,0 +1,373 @@
*-- 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

BIN
nfjson/nfjsonread.FXP Normal file

Binary file not shown.