Fix Associazione ricevute

This commit is contained in:
2026-03-19 16:55:31 +01:00
parent 3bdb32e6ae
commit c34d6bb080
15 changed files with 59 additions and 44 deletions
+8 -8
View File
@@ -1,5 +1,5 @@
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
# PecFlow Variabili d'ambiente # PEChub Variabili d'ambiente
# Copia questo file in .env e personalizza i valori # Copia questo file in .env e personalizza i valori
# NON committare mai il file .env con valori reali # NON committare mai il file .env con valori reali
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
@@ -25,12 +25,12 @@ ENCRYPTION_KEY=change-me-generate-a-random-64-char-hex-string-here-11111111111
# ── Database PostgreSQL ─────────────────────────────────────────────────────── # ── Database PostgreSQL ───────────────────────────────────────────────────────
POSTGRES_HOST=db POSTGRES_HOST=db
POSTGRES_PORT=5432 POSTGRES_PORT=5432
POSTGRES_DB=pecflow POSTGRES_DB=pechub
POSTGRES_USER=pecflow POSTGRES_USER=pechub
POSTGRES_PASSWORD=pecflow_dev_password POSTGRES_PASSWORD=pechub_dev_password
DATABASE_URL=postgresql+asyncpg://pecflow:pecflow_dev_password@db:5432/pecflow DATABASE_URL=postgresql+asyncpg://pechub:pechub_dev_password@db:5432/pechub
DATABASE_URL_SYNC=postgresql://pecflow:pecflow_dev_password@db:5432/pecflow DATABASE_URL_SYNC=postgresql://pechub:pechub_dev_password@db:5432/pechub
# ── Redis ───────────────────────────────────────────────────────────────────── # ── Redis ─────────────────────────────────────────────────────────────────────
REDIS_URL=redis://redis:6379/0 REDIS_URL=redis://redis:6379/0
@@ -39,7 +39,7 @@ REDIS_URL=redis://redis:6379/0
MINIO_ENDPOINT=minio:9000 MINIO_ENDPOINT=minio:9000
MINIO_ACCESS_KEY=minioadmin MINIO_ACCESS_KEY=minioadmin
MINIO_SECRET_KEY=minioadmin MINIO_SECRET_KEY=minioadmin
MINIO_BUCKET=pecflow MINIO_BUCKET=pechub
MINIO_USE_SSL=false MINIO_USE_SSL=false
# ── CORS ────────────────────────────────────────────────────────────────────── # ── CORS ──────────────────────────────────────────────────────────────────────
@@ -58,4 +58,4 @@ SYSTEM_SMTP_HOST=
SYSTEM_SMTP_PORT=587 SYSTEM_SMTP_PORT=587
SYSTEM_SMTP_USER= SYSTEM_SMTP_USER=
SYSTEM_SMTP_PASSWORD= SYSTEM_SMTP_PASSWORD=
SYSTEM_SMTP_FROM=noreply@pecflow.it SYSTEM_SMTP_FROM=noreply@pechub.it
+12 -12
View File
@@ -1,4 +1,4 @@
# PecFlow Architettura di Sistema # PEChub Architettura di Sistema
> **Documento redatto il:** 2026-03-18 | **Ultima revisione:** 2026-03-18 > **Documento redatto il:** 2026-03-18 | **Ultima revisione:** 2026-03-18
> **Versione:** 2.0 > **Versione:** 2.0
@@ -26,7 +26,7 @@ Monorepo con workspace separati. Il confine di responsabilità è netto: ogni
cartella di primo livello è un deployable (o una libreria condivisa) indipendente. cartella di primo livello è un deployable (o una libreria condivisa) indipendente.
``` ```
PecFlow/ ← root del monorepo PEChub/ ← root del monorepo
├── .github/ ← CI/CD GitHub Actions ├── .github/ ← CI/CD GitHub Actions
│ ├── workflows/ │ ├── workflows/
@@ -279,14 +279,14 @@ PecFlow/ ← root del monorepo
│ ├── nginx/ │ ├── nginx/
│ │ ├── nginx.conf # reverse proxy, rate limiting, TLS termination │ │ ├── nginx.conf # reverse proxy, rate limiting, TLS termination
│ │ └── conf.d/ │ │ └── conf.d/
│ │ └── pecflow.conf │ │ └── pechub.conf
│ ├── redis/ │ ├── redis/
│ │ └── redis.conf # maxmemory, eviction policy │ │ └── redis.conf # maxmemory, eviction policy
│ ├── prometheus/ │ ├── prometheus/
│ │ └── prometheus.yml │ │ └── prometheus.yml
│ └── grafana/ │ └── grafana/
│ └── dashboards/ │ └── dashboards/
│ └── pecflow.json │ └── pechub.json
└── docs/ ═══════════════════════════════ └── docs/ ═══════════════════════════════
├── api/ ← OpenAPI spec generata (non commitare auto-gen) ├── api/ ← OpenAPI spec generata (non commitare auto-gen)
@@ -326,7 +326,7 @@ CREATE EXTENSION IF NOT EXISTS "pgcrypto"; -- gen_random_bytes per nonce AES
-- ============================================================ -- ============================================================
CREATE TABLE tenants ( CREATE TABLE tenants (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
slug VARCHAR(63) NOT NULL UNIQUE, -- usato come subdomain: acme.pecflow.it slug VARCHAR(63) NOT NULL UNIQUE, -- usato come subdomain: acme.pechub.it
name VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL,
plan VARCHAR(50) NOT NULL DEFAULT 'starter', -- starter|pro|enterprise plan VARCHAR(50) NOT NULL DEFAULT 'starter', -- starter|pro|enterprise
is_active BOOLEAN NOT NULL DEFAULT TRUE, is_active BOOLEAN NOT NULL DEFAULT TRUE,
@@ -1000,7 +1000,7 @@ Il conservatore AgID può essere temporaneamente non disponibile (manutenzione,
**Strategia:** **Strategia:**
1. **Backoff esponenziale con jitter:** tentativo 1 subito, poi 1h, 4h, 24h (max 3 retry oltre il primo) 1. **Backoff esponenziale con jitter:** tentativo 1 subito, poi 1h, 4h, 24h (max 3 retry oltre il primo)
2. **Dead letter queue:** dopo tutti i retry falliti, il batch va in stato `failed` e genera un alert email all'admin del tenant + al team ops PecFlow 2. **Dead letter queue:** dopo tutti i retry falliti, il batch va in stato `failed` e genera un alert email all'admin del tenant + al team ops PEChub
3. **Idempotenza:** prima di ogni retry, verificare se il conservatore ha già ricevuto il versamento (`GET /versamento/{id}`) per evitare duplicati 3. **Idempotenza:** prima di ogni retry, verificare se il conservatore ha già ricevuto il versamento (`GET /versamento/{id}`) per evitare duplicati
4. **Finestra di versamento:** i versamenti periodici (es. mensili) devono completarsi entro la scadenza normativa; se il sistema prevede che non ce la farà, genera alert anticipato a 72h dalla scadenza 4. **Finestra di versamento:** i versamenti periodici (es. mensili) devono completarsi entro la scadenza normativa; se il sistema prevede che non ce la farà, genera alert anticipato a 72h dalla scadenza
5. **Circuit breaker:** se il conservatore fallisce 5 volte in 1 ora, si sospendono tutti i versamenti verso quel conservatore per 30 minuti (evita tempesta di retry) 5. **Circuit breaker:** se il conservatore fallisce 5 volte in 1 ora, si sospendono tutti i versamenti verso quel conservatore per 30 minuti (evita tempesta di retry)
@@ -1418,7 +1418,7 @@ alla Fase 1, le altre possono essere sviluppate in parallelo al frontend (Fase 5
- [ ] API `POST /notifications/rules` (crea regola evento → canale + filtri) - [ ] API `POST /notifications/rules` (crea regola evento → canale + filtri)
- [ ] `notification_service.py`: al salvataggio di ogni messaggio, valuta regole applicabili e accoda job - [ ] `notification_service.py`: al salvataggio di ogni messaggio, valuta regole applicabili e accoda job
- [ ] `worker/notifications/dispatcher.py`: smista per tipo canale - [ ] `worker/notifications/dispatcher.py`: smista per tipo canale
- [ ] `webhook.py`: POST JSON con header `X-PecFlow-Signature: sha256=<HMAC>` per verifica autenticità - [ ] `webhook.py`: POST JSON con header `X-PEChub-Signature: sha256=<HMAC>` per verifica autenticità
- [ ] `email_smtp.py`: template HTML notifica (oggetto, mittente, link messaggio) - [ ] `email_smtp.py`: template HTML notifica (oggetto, mittente, link messaggio)
- [ ] `telegram.py`: messaggio Telegram con MarkdownV2, link deep al messaggio - [ ] `telegram.py`: messaggio Telegram con MarkdownV2, link deep al messaggio
- [ ] `whatsapp.py`: Meta Cloud API `POST /messages` con template pre-approvato (o freeform in 24h window) - [ ] `whatsapp.py`: Meta Cloud API `POST /messages` con template pre-approvato (o freeform in 24h window)
@@ -1668,10 +1668,10 @@ Evento PEC (trigger: salvataggio nuovo messaggio o cambio stato)
**Webhook** **Webhook**
```json ```json
POST https://your-endpoint.com/pecflow-hook POST https://your-endpoint.com/pechub-hook
Headers: Headers:
X-PecFlow-Event: message.received X-PEChub-Event: message.received
X-PecFlow-Signature: sha256=<HMAC-SHA256(secret, body)> X-PEChub-Signature: sha256=<HMAC-SHA256(secret, body)>
Content-Type: application/json Content-Type: application/json
Body: Body:
@@ -1685,7 +1685,7 @@ Body:
"mailbox": "info@azienda.it", "mailbox": "info@azienda.it",
"received_at": "2026-03-18T14:00:00Z", "received_at": "2026-03-18T14:00:00Z",
"state": "received", "state": "received",
"url": "https://app.pecflow.it/messages/..." "url": "https://app.pechub.it/messages/..."
} }
} }
``` ```
@@ -1702,7 +1702,7 @@ Notifica in HTML via template configurabile. Il mittente usa un relay SMTP dedic
📋 Oggetto: Convocazione riunione del 20/03/2026 📋 Oggetto: Convocazione riunione del 20/03/2026
🕐 Ricevuta: 18/03/2026 14:00 🕐 Ricevuta: 18/03/2026 14:00
🔗 Visualizza: https://app.pecflow.it/messages/... 🔗 Visualizza: https://app.pechub.it/messages/...
``` ```
**WhatsApp (Meta Cloud API)** **WhatsApp (Meta Cloud API)**
+3 -3
View File
@@ -26,7 +26,7 @@ Tutto il frontend deve essere in italiano
Credenziali admin Credenziali admin
Ruolo Email Password Ruolo Email Password
Super Admin superadmin@pecflow.it SuperAdmin@PecFlow2026! Super Admin superadmin@pechub.it SuperAdmin@PEChub2026!
Admin (tenant demo) admin@demo.pecflow.it Demo@PecFlow2026! Admin (tenant demo) admin@demo.pechub.it Demo@PEChub2026!
Operator (tenant demo) operator@demo.pecflow.it Oper@PecFlow2026! Operator (tenant demo) operator@demo.pechub.it Oper@PEChub2026!
Per accedere all'applicazione usa le credenziali Admin del tenant demo. Per accedere all'applicazione usa le credenziali Admin del tenant demo.
+3 -3
View File
@@ -1,4 +1,4 @@
## PecFlow Developer Commands ## PEChub Developer Commands
.PHONY: dev down build test migrate seed lint format clean logs ps help .PHONY: dev down build test migrate seed lint format clean logs ps help
@@ -79,7 +79,7 @@ makemigration: ## Genera una nuova migrazione (usa: make makemigration MSG="des
$(BACKEND) alembic revision --autogenerate -m "$(MSG)" $(BACKEND) alembic revision --autogenerate -m "$(MSG)"
seed: ## Esegui seed dati di sviluppo (tenant demo + admin) seed: ## Esegui seed dati di sviluppo (tenant demo + admin)
$(COMPOSE) exec db psql -U pecflow -d pecflow -f /docker-entrypoint-initdb.d/seeds/dev_tenant.sql $(COMPOSE) exec db psql -U pechub -d pechub -f /docker-entrypoint-initdb.d/seeds/dev_tenant.sql
@echo " ✅ Seed completato" @echo " ✅ Seed completato"
reset-db: ## Reset completo DB (down-v + dev + migrate + seed) reset-db: ## Reset completo DB (down-v + dev + migrate + seed)
@@ -151,7 +151,7 @@ shell-backend: ## Shell nel container backend
$(BACKEND) bash $(BACKEND) bash
shell-db: ## psql nel container database shell-db: ## psql nel container database
$(COMPOSE) exec db psql -U pecflow -d pecflow $(COMPOSE) exec db psql -U pechub -d pechub
clean: ## Rimuovi file temporanei Python clean: ## Rimuovi file temporanei Python
find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true
+18 -3
View File
@@ -362,8 +362,16 @@ async def bulk_update_messages(
message.archived_at = None message.archived_at = None
await db.commit() await db.commit()
for message in messages:
await db.refresh(message) # Ricarica i messaggi aggiornati con selectinload per evitare MissingGreenlet sui labels
if messages:
updated_ids = [m.id for m in messages]
refreshed_result = await db.execute(
select(Message)
.where(Message.id.in_(updated_ids))
.options(selectinload(Message.labels))
)
messages = list(refreshed_result.scalars().all())
return MessageBulkUpdateResponse( return MessageBulkUpdateResponse(
updated=len(messages), updated=len(messages),
@@ -407,7 +415,13 @@ async def update_message(
message.archived_at = None message.archived_at = None
await db.commit() await db.commit()
await db.refresh(message) # Re-query con selectinload per evitare MissingGreenlet sui labels
refreshed = await db.execute(
select(Message)
.where(Message.id == message_id)
.options(selectinload(Message.labels))
)
message = refreshed.scalar_one()
return MessageResponse.model_validate(message) return MessageResponse.model_validate(message)
@@ -508,6 +522,7 @@ async def list_receipts(
result = await db.execute( result = await db.execute(
select(Message) select(Message)
.where(Message.parent_message_id == message_id) .where(Message.parent_message_id == message_id)
.options(selectinload(Message.labels))
.order_by(Message.received_at.asc().nullslast(), Message.created_at.asc()) .order_by(Message.received_at.asc().nullslast(), Message.created_at.asc())
) )
receipts = list(result.scalars().all()) receipts = list(result.scalars().all())
+2 -2
View File
@@ -4,8 +4,8 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>PecFlow Gestore PEC</title> <title>PEChub Gestore PEC</title>
<meta name="description" content="PecFlow - Piattaforma SaaS per la gestione della posta elettronica certificata" /> <meta name="description" content="PEChub - Piattaforma SaaS per la gestione della posta elettronica certificata" />
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
+1 -1
View File
@@ -1,5 +1,5 @@
{ {
"name": "pecflow-frontend", "name": "pechub-frontend",
"version": "1.0.0", "version": "1.0.0",
"private": true, "private": true,
"type": "module", "type": "module",
+1 -1
View File
@@ -12,7 +12,7 @@ import { VirtualBoxesPage } from '@/pages/VirtualBoxes/VirtualBoxesPage'
import { NotificationsPage } from '@/pages/Notifications/NotificationsPage' import { NotificationsPage } from '@/pages/Notifications/NotificationsPage'
/** /**
* Routing principale dell'applicazione PecFlow. * Routing principale dell'applicazione PEChub.
* *
* Struttura: * Struttura:
* - /login → LoginPage (pubblica) * - /login → LoginPage (pubblica)
+3 -3
View File
@@ -1,9 +1,9 @@
/** /**
* Sidebar navigazione principale di PecFlow. * Sidebar navigazione principale di PEChub.
* *
* Struttura visiva (sidebar espansa): * Struttura visiva (sidebar espansa):
* ┌────────────────────────────────┐ * ┌────────────────────────────────┐
* │ [PF] PecFlow [◀] │ * │ [PF] PEChub [◀] │
* ├────────────────────────────────┤ * ├────────────────────────────────┤
* │ TUTTE LE CASELLE │ * │ TUTTE LE CASELLE │
* │ 📥 Posta in Arrivo [badge] │ * │ 📥 Posta in Arrivo [badge] │
@@ -140,7 +140,7 @@ export function Sidebar() {
<div className="h-8 w-8 rounded-lg bg-blue-500 flex items-center justify-center text-white font-bold text-sm flex-shrink-0"> <div className="h-8 w-8 rounded-lg bg-blue-500 flex items-center justify-center text-white font-bold text-sm flex-shrink-0">
PF PF
</div> </div>
<span className="font-bold text-lg">PecFlow</span> <span className="font-bold text-lg">PEChub</span>
</div> </div>
)} )}
{collapsed && ( {collapsed && (
+1 -1
View File
@@ -1,5 +1,5 @@
/** /**
* Hook useWebSocket connessione WebSocket al backend PecFlow. * Hook useWebSocket connessione WebSocket al backend PEChub.
* *
* Il backend usa FastAPI WebSocket nativo (non Socket.io). * Il backend usa FastAPI WebSocket nativo (non Socket.io).
* Endpoint: /api/v1/ws/{tenant_id}?token=<access_token> * Endpoint: /api/v1/ws/{tenant_id}?token=<access_token>
+2 -2
View File
@@ -66,7 +66,7 @@ export function LoginPage() {
<div className="mx-auto h-16 w-16 rounded-2xl bg-blue-600 flex items-center justify-center shadow-lg"> <div className="mx-auto h-16 w-16 rounded-2xl bg-blue-600 flex items-center justify-center shadow-lg">
<span className="text-white font-bold text-2xl">PF</span> <span className="text-white font-bold text-2xl">PF</span>
</div> </div>
<h1 className="mt-4 text-3xl font-bold text-gray-900">PecFlow</h1> <h1 className="mt-4 text-3xl font-bold text-gray-900">PEChub</h1>
<p className="mt-1 text-sm text-gray-500">Gestore PEC SaaS</p> <p className="mt-1 text-sm text-gray-500">Gestore PEC SaaS</p>
</div> </div>
@@ -188,7 +188,7 @@ export function LoginPage() {
</Card> </Card>
<p className="text-center text-xs text-gray-400"> <p className="text-center text-xs text-gray-400">
PecFlow v1.0 Piattaforma gestione PEC certificata PEChub v1.0 Piattaforma gestione PEC certificata
</p> </p>
</div> </div>
</div> </div>
@@ -447,7 +447,7 @@ function RulesTab() {
<Zap className="h-12 w-12 text-muted-foreground mb-4 opacity-40" /> <Zap className="h-12 w-12 text-muted-foreground mb-4 opacity-40" />
<p className="text-lg font-medium text-muted-foreground">Nessuna regola</p> <p className="text-lg font-medium text-muted-foreground">Nessuna regola</p>
<p className="text-sm text-muted-foreground mt-1"> <p className="text-sm text-muted-foreground mt-1">
Le regole collegano gli eventi PecFlow ai canali di notifica. Le regole collegano gli eventi PEChub ai canali di notifica.
</p> </p>
</div> </div>
) )
+1 -1
View File
@@ -99,7 +99,7 @@ export const useAuthStore = create<AuthState>()(
}) })
}, },
}), }),
{ name: 'PecFlow/Auth' }, { name: 'PEChub/Auth' },
), ),
) )
+1 -1
View File
@@ -1,4 +1,4 @@
# Redis configuration per PecFlow # Redis configuration per PEChub
# ── Bind e rete ─────────────────────────────────────────────────────────────── # ── Bind e rete ───────────────────────────────────────────────────────────────
bind 0.0.0.0 bind 0.0.0.0
+2 -2
View File
@@ -3,9 +3,9 @@ requires = ["setuptools>=68", "wheel"]
build-backend = "setuptools.build_meta" build-backend = "setuptools.build_meta"
[project] [project]
name = "pecflow-worker" name = "pechub-worker"
version = "1.0.0" version = "1.0.0"
description = "PecFlow Worker IMAP sync + background jobs" description = "PEChub Worker IMAP sync + background jobs"
requires-python = ">=3.12" requires-python = ">=3.12"
dependencies = [ dependencies = [