feat: normaliser les URL recette par entité
Some checks failed
Validation applicative / TypeScript, tests et build (push) Failing after 2m55s

This commit is contained in:
Santinova Deployment
2026-10-24 17:06:48 +00:00
parent 16759b4ad5
commit fd970db7fb
6 changed files with 19 additions and 19 deletions

View File

@@ -3,8 +3,8 @@
"name": "Démat. Facturation DSI", "name": "Démat. Facturation DSI",
"category": "SANTINOVA", "category": "SANTINOVA",
"urls": { "urls": {
"recette": "https://demat-facturation.recette.santinova-soft.org", "recette": "https://demat-facturation-santinova.recette.santinova-soft.org",
"prod": "https://demat-facturation.santinova-soft.org" "prod": "https://demat-facturation-santinova.santinova-soft.org"
}, },
"containerName": "demat-facturation-app", "containerName": "demat-facturation-app",
"image": "images/demat-facturation-dsi.jpg", "image": "images/demat-facturation-dsi.jpg",

View File

@@ -59,7 +59,7 @@ services:
JWT_SECRET: demat-facturation-jwt-secret-recette-2026 JWT_SECRET: demat-facturation-jwt-secret-recette-2026
MISTRAL_API_KEY: ${MISTRAL_API_KEY:-} MISTRAL_API_KEY: ${MISTRAL_API_KEY:-}
# Passerelle centralisée : le jeton reste dans l'environnement serveur. # Passerelle centralisée : le jeton reste dans l'environnement serveur.
REFERENTIEL_IA_URL: ${REFERENTIEL_IA_URL:-https://referentiel-ia.recette.santinova-soft.org} REFERENTIEL_IA_URL: ${REFERENTIEL_IA_URL:-https://referentiel-ia-santinova.recette.santinova-soft.org}
REFERENTIEL_IA_TOKEN: ${REFERENTIEL_IA_TOKEN:-} REFERENTIEL_IA_TOKEN: ${REFERENTIEL_IA_TOKEN:-}
REFERENTIEL_IA_TOKEN_FILE: /run/secrets/referentiel_ia_token REFERENTIEL_IA_TOKEN_FILE: /run/secrets/referentiel_ia_token
UPLOAD_DIR: /app/uploads UPLOAD_DIR: /app/uploads
@@ -95,7 +95,7 @@ services:
priority: 100 priority: 100
labels: labels:
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.http.routers.demat-facturation.rule=Host(`${APP_DOMAIN:-demat-facturation.recette.santinova-soft.org}`)" - "traefik.http.routers.demat-facturation.rule=Host(`demat-facturation.recette.santinova-soft.org`) || Host(`demat-facturation-santinova.recette.santinova-soft.org`)"
- "traefik.http.routers.demat-facturation.entrypoints=websecure" - "traefik.http.routers.demat-facturation.entrypoints=websecure"
- "traefik.http.routers.demat-facturation.tls.certresolver=letsencrypt" - "traefik.http.routers.demat-facturation.tls.certresolver=letsencrypt"
- "traefik.http.services.demat-facturation.loadbalancer.server.port=3000" - "traefik.http.services.demat-facturation.loadbalancer.server.port=3000"

View File

@@ -23,7 +23,7 @@ Contenu de `config.json` :
{ {
"sfrLogin": "votre-login@sfr.fr", "sfrLogin": "votre-login@sfr.fr",
"sfrPassword": "votre-mot-de-passe", "sfrPassword": "votre-mot-de-passe",
"appUrl": "https://demat-facturation.santinova-soft.org", "appUrl": "https://demat-facturation-santinova.santinova-soft.org",
"apiToken": "votre-token-api-copié-depuis-lappli", "apiToken": "votre-token-api-copié-depuis-lappli",
"downloadDir": "/tmp/sfr-invoices", "downloadDir": "/tmp/sfr-invoices",
"processedFile": "/tmp/sfr-processed.json" "processedFile": "/tmp/sfr-processed.json"
@@ -34,7 +34,7 @@ Ou utiliser des variables d'environnement :
```bash ```bash
export SFR_LOGIN=votre-login@sfr.fr export SFR_LOGIN=votre-login@sfr.fr
export SFR_PASSWORD=votre-mot-de-passe export SFR_PASSWORD=votre-mot-de-passe
export APP_URL=https://demat-facturation.santinova-soft.org export APP_URL=https://demat-facturation-santinova.santinova-soft.org
export API_TOKEN=votre-token-api export API_TOKEN=votre-token-api
``` ```

View File

@@ -1,16 +1,16 @@
/** /**
* Connecteur SFR Pro - Script cron pour import automatique de factures * Connecteur SFR Pro - Script cron pour import automatique de factures
* *
* Prérequis sur le serveur LWS : * Prérequis sur le serveur LWS :
* npm install playwright @playwright/test * npm install playwright @playwright/test
* npx playwright install chromium * npx playwright install chromium
* *
* Configuration : * Configuration :
* Copier .env.example en .env et remplir les variables * Copier .env.example en .env et remplir les variables
* *
* Utilisation : * Utilisation :
* node sfr-connector.mjs * node sfr-connector.mjs
* *
* Cron (mensuel le 5 du mois à 8h) : * Cron (mensuel le 5 du mois à 8h) :
* 0 8 5 * * /usr/bin/node /opt/web-import/sfr-connector.mjs >> /var/log/sfr-import.log 2>&1 * 0 8 5 * * /usr/bin/node /opt/web-import/sfr-connector.mjs >> /var/log/sfr-import.log 2>&1
*/ */
@@ -33,7 +33,7 @@ const CONFIG = {
login: process.env.SFR_LOGIN || '', login: process.env.SFR_LOGIN || '',
password: process.env.SFR_PASSWORD || '', password: process.env.SFR_PASSWORD || '',
// URL de l'application de dématérialisation // URL de l'application de dématérialisation
appUrl: process.env.APP_URL || 'https://demat-facturation.santinova-soft.org', appUrl: process.env.APP_URL || 'https://demat-facturation-santinova.santinova-soft.org',
// Token API de la source web (récupéré depuis l'interface Connecteurs web) // Token API de la source web (récupéré depuis l'interface Connecteurs web)
apiToken: process.env.API_TOKEN || '', apiToken: process.env.API_TOKEN || '',
// Dossier temporaire pour les PDFs téléchargés // Dossier temporaire pour les PDFs téléchargés
@@ -63,7 +63,7 @@ function saveProcessed(list) {
async function pushInvoiceToApp(filePath, fileName) { async function pushInvoiceToApp(filePath, fileName) {
const fileBuffer = fs.readFileSync(filePath); const fileBuffer = fs.readFileSync(filePath);
const fileBase64 = fileBuffer.toString('base64'); const fileBase64 = fileBuffer.toString('base64');
const body = JSON.stringify({ const body = JSON.stringify({
apiToken: CONFIG.apiToken, apiToken: CONFIG.apiToken,
fileName, fileName,
@@ -104,7 +104,7 @@ async function pushInvoiceToApp(filePath, fileName) {
// ============ CONNECTEUR SFR ============ // ============ CONNECTEUR SFR ============
async function runSfrConnector() { async function runSfrConnector() {
log('Démarrage du connecteur SFR Pro'); log('Démarrage du connecteur SFR Pro');
if (!CONFIG.login || !CONFIG.password || !CONFIG.apiToken) { if (!CONFIG.login || !CONFIG.password || !CONFIG.apiToken) {
log('ERREUR : SFR_LOGIN, SFR_PASSWORD et API_TOKEN sont requis'); log('ERREUR : SFR_LOGIN, SFR_PASSWORD et API_TOKEN sont requis');
process.exit(1); process.exit(1);
@@ -140,14 +140,14 @@ async function runSfrConnector() {
await page.fill('input[type="email"], input[name="login"], input[id*="login"], input[id*="email"]', CONFIG.login); await page.fill('input[type="email"], input[name="login"], input[id*="login"], input[id*="email"]', CONFIG.login);
await page.fill('input[type="password"], input[name="password"], input[id*="password"]', CONFIG.password); await page.fill('input[type="password"], input[name="password"], input[id*="password"]', CONFIG.password);
await page.click('button[type="submit"], input[type="submit"], button:has-text("Connexion"), button:has-text("Se connecter")'); await page.click('button[type="submit"], input[type="submit"], button:has-text("Connexion"), button:has-text("Se connecter")');
await page.waitForNavigation({ waitUntil: 'networkidle', timeout: 15000 }).catch(() => {}); await page.waitForNavigation({ waitUntil: 'networkidle', timeout: 15000 }).catch(() => {});
log('Connecté'); log('Connecté');
// 4. Naviguer vers la section factures // 4. Naviguer vers la section factures
// Adapter selon la structure réelle du site SFR Pro // Adapter selon la structure réelle du site SFR Pro
await page.goto(`${CONFIG.portalUrl}factures`, { waitUntil: 'networkidle', timeout: 15000 }).catch(() => {}); await page.goto(`${CONFIG.portalUrl}factures`, { waitUntil: 'networkidle', timeout: 15000 }).catch(() => {});
// Chercher les liens de factures PDF // Chercher les liens de factures PDF
const invoiceLinks = await page.$$eval( const invoiceLinks = await page.$$eval(
'a[href*=".pdf"], a[href*="facture"], a[href*="invoice"], a[download]', 'a[href*=".pdf"], a[href*="facture"], a[href*="invoice"], a[download]',

View File

@@ -39,7 +39,7 @@ describe("routage et persistance de déploiement", () => {
it("injecte uniquement les secrets serveur de la passerelle Référentiel IA", () => { it("injecte uniquement les secrets serveur de la passerelle Référentiel IA", () => {
const composeFile = readFileSync(join(process.cwd(), "docker-compose.yml"), "utf8"); const composeFile = readFileSync(join(process.cwd(), "docker-compose.yml"), "utf8");
expect(composeFile).toContain("REFERENTIEL_IA_URL: ${REFERENTIEL_IA_URL:-https://referentiel-ia.recette.santinova-soft.org}"); expect(composeFile).toContain("REFERENTIEL_IA_URL: ${REFERENTIEL_IA_URL:-https://referentiel-ia-santinova.recette.santinova-soft.org}");
expect(composeFile).toContain("REFERENTIEL_IA_TOKEN: ${REFERENTIEL_IA_TOKEN:-}"); expect(composeFile).toContain("REFERENTIEL_IA_TOKEN: ${REFERENTIEL_IA_TOKEN:-}");
expect(composeFile).toContain("REFERENTIEL_IA_TOKEN_FILE: /run/secrets/referentiel_ia_token"); expect(composeFile).toContain("REFERENTIEL_IA_TOKEN_FILE: /run/secrets/referentiel_ia_token");
expect(composeFile).toContain("file: /opt/manus-secrets/demat-facturation-referentiel-ia-token"); expect(composeFile).toContain("file: /opt/manus-secrets/demat-facturation-referentiel-ia-token");

View File

@@ -10,7 +10,7 @@ import {
import { readSecretFile, resolveServerSecret } from "./_core/env"; import { readSecretFile, resolveServerSecret } from "./_core/env";
const connection: ReferentielIaConnection = { const connection: ReferentielIaConnection = {
baseUrl: "https://referentiel-ia.recette.santinova-soft.org", baseUrl: "https://referentiel-ia-santinova.recette.santinova-soft.org",
token: "test-token", token: "test-token",
}; };
@@ -63,7 +63,7 @@ describe("Référentiel IA", () => {
receivedUrl = String(url); receivedUrl = String(url);
receivedInit = init; receivedInit = init;
if (callCount === 1) { if (callCount === 1) {
expect(receivedUrl).toBe("https://referentiel-ia.recette.santinova-soft.org/api/integration/v1/recommendations"); expect(receivedUrl).toBe("https://referentiel-ia-santinova.recette.santinova-soft.org/api/integration/v1/recommendations");
expect(JSON.parse(String(receivedInit?.body))).toEqual({ expect(JSON.parse(String(receivedInit?.body))).toEqual({
useCase: "texte", useCase: "texte",
optimizeCost: true, optimizeCost: true,
@@ -84,7 +84,7 @@ describe("Référentiel IA", () => {
}, },
); );
expect(receivedUrl).toBe("https://referentiel-ia.recette.santinova-soft.org/api/integration/v1/invoke"); expect(receivedUrl).toBe("https://referentiel-ia-santinova.recette.santinova-soft.org/api/integration/v1/invoke");
expect(receivedInit?.headers).toMatchObject({ authorization: "Bearer test-token" }); expect(receivedInit?.headers).toMatchObject({ authorization: "Bearer test-token" });
expect(JSON.parse(String(receivedInit?.body))).toMatchObject({ expect(JSON.parse(String(receivedInit?.body))).toMatchObject({
engineSlug: "santino-expert", engineSlug: "santino-expert",