Merge remote changes, keeping local versions of conflict files
This commit is contained in:
150
README-ORACLE-MODES.md
Normal file
150
README-ORACLE-MODES.md
Normal 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
15
api/.env.example
Normal 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
43
api/01_create_table.sql
Normal 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
38
api/Dockerfile
Normal 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
250
api/admin.py
Normal 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
5
api/requirements.txt
Normal 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
9
api/tnsnames.ora
Normal 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
37
docker-compose.yaml
Normal 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
|
||||||
222
docs/LLM_PROJECT_MANAGER_PROMPT.md
Normal file
222
docs/LLM_PROJECT_MANAGER_PROMPT.md
Normal 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
319
docs/PRD.md
Normal 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
373
gomag-vending-test.prg
Normal 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
BIN
nfjson/nfjsonread.FXP
Normal file
Binary file not shown.
Reference in New Issue
Block a user