espace-paie-odentas/VIREMENTS_SALAIRES_SQL_QUERIES.md

441 lines
12 KiB
Markdown

# Requêtes SQL utiles - Virements Salaires
## Consultation des données
### Lister tous les virements avec détails de l'organisation
```sql
SELECT
st.id,
st.period_month,
st.period_label,
st.deadline,
st.mode,
st.total_net,
st.callsheet_url,
st.notification_sent,
st.notification_ok,
st.client_wire_received_at,
st.notes,
st.created_at,
o.name as organization_name
FROM salary_transfers st
LEFT JOIN organizations o ON o.id = st.org_id
ORDER BY st.period_month DESC, st.created_at DESC;
```
### Virements par organisation
```sql
SELECT
o.name as organization_name,
COUNT(st.id) as total_virements,
COUNT(st.callsheet_url) as virements_avec_pdf,
SUM(CASE WHEN st.callsheet_url IS NULL THEN 1 ELSE 0 END) as virements_sans_pdf,
SUM(CAST(st.total_net AS NUMERIC)) as total_montant
FROM organizations o
LEFT JOIN salary_transfers st ON st.org_id = o.id
GROUP BY o.id, o.name
ORDER BY total_virements DESC;
```
### Virements pour un mois spécifique
```sql
SELECT
st.*,
o.name as organization_name
FROM salary_transfers st
LEFT JOIN organizations o ON o.id = st.org_id
WHERE DATE_TRUNC('month', st.period_month) = '2025-01-01'::date
ORDER BY st.deadline ASC;
```
### Virements en attente de PDF
```sql
SELECT
st.id,
st.period_label,
o.name as organization_name,
st.deadline,
st.created_at
FROM salary_transfers st
LEFT JOIN organizations o ON o.id = st.org_id
WHERE st.callsheet_url IS NULL
ORDER BY st.deadline ASC;
```
### Virements avec notifications en erreur
```sql
SELECT
st.id,
st.period_label,
o.name as organization_name,
st.notification_sent,
st.notification_ok,
st.notes
FROM salary_transfers st
LEFT JOIN organizations o ON o.id = st.org_id
WHERE st.notification_sent = true
AND st.notification_ok = false
ORDER BY st.created_at DESC;
```
## Contrats associés à un virement
### Contrats pour une période donnée (logique de filtrage)
```sql
-- Récupérer tous les contrats qui seront inclus dans un PDF
-- pour une organisation et un mois donnés
SELECT
c.id,
c.employee_first_name,
c.employee_last_name,
c.employee_iban,
c.payment_date,
c.net_amount,
c.gross_amount
FROM cddu_contracts c
WHERE c.org_id = 'ORG_ID_ICI'
AND c.payment_date IS NOT NULL
AND DATE_TRUNC('month', c.payment_date) = '2025-01-01'::date
ORDER BY c.employee_last_name, c.employee_first_name;
```
### Calculer le total net pour une période
```sql
SELECT
DATE_TRUNC('month', c.payment_date) as mois,
COUNT(c.id) as nombre_contrats,
SUM(CAST(c.net_amount AS NUMERIC)) as total_net,
SUM(CAST(c.gross_amount AS NUMERIC)) as total_brut
FROM cddu_contracts c
WHERE c.org_id = 'ORG_ID_ICI'
AND c.payment_date IS NOT NULL
AND DATE_TRUNC('month', c.payment_date) = '2025-01-01'::date
GROUP BY DATE_TRUNC('month', c.payment_date);
```
### Contrats par virement (avec jointure)
```sql
SELECT
st.id as virement_id,
st.period_label,
c.id as contrat_id,
c.employee_first_name,
c.employee_last_name,
c.payment_date,
c.net_amount
FROM salary_transfers st
LEFT JOIN cddu_contracts c ON (
c.org_id = st.org_id
AND DATE_TRUNC('month', c.payment_date) = DATE_TRUNC('month', st.period_month)
AND c.payment_date IS NOT NULL
)
WHERE st.id = 'VIREMENT_ID_ICI'
ORDER BY c.employee_last_name, c.employee_first_name;
```
### Contrats sans IBAN pour une période
```sql
-- Utile pour détecter les problèmes avant génération
SELECT
c.id,
c.employee_first_name,
c.employee_last_name,
c.employee_iban,
c.payment_date,
c.net_amount
FROM cddu_contracts c
WHERE c.org_id = 'ORG_ID_ICI'
AND c.payment_date IS NOT NULL
AND DATE_TRUNC('month', c.payment_date) = '2025-01-01'::date
AND (c.employee_iban IS NULL OR c.employee_iban = '')
ORDER BY c.employee_last_name;
```
## Statistiques et rapports
### Résumé mensuel par organisation
```sql
SELECT
o.name as organisation,
TO_CHAR(st.period_month, 'YYYY-MM') as mois,
COUNT(st.id) as nb_virements,
SUM(CAST(st.total_net AS NUMERIC)) as total_montant,
COUNT(st.callsheet_url) as nb_pdf_generes,
COUNT(CASE WHEN st.notification_sent = true THEN 1 END) as nb_notifications_envoyees,
COUNT(CASE WHEN st.client_wire_received_at IS NOT NULL THEN 1 END) as nb_virements_recus
FROM organizations o
LEFT JOIN salary_transfers st ON st.org_id = o.id
WHERE st.period_month >= '2025-01-01'
GROUP BY o.id, o.name, TO_CHAR(st.period_month, 'YYYY-MM')
ORDER BY mois DESC, organisation ASC;
```
### Virements par mode de paiement
```sql
SELECT
st.mode,
COUNT(st.id) as nombre,
SUM(CAST(st.total_net AS NUMERIC)) as total_montant,
AVG(CAST(st.total_net AS NUMERIC)) as montant_moyen
FROM salary_transfers st
GROUP BY st.mode
ORDER BY nombre DESC;
```
### Délais entre création et génération PDF
```sql
SELECT
st.id,
st.period_label,
o.name as organisation,
st.created_at,
st.updated_at,
st.callsheet_url,
EXTRACT(EPOCH FROM (st.updated_at - st.created_at)) / 3600 as heures_ecart
FROM salary_transfers st
LEFT JOIN organizations o ON o.id = st.org_id
WHERE st.callsheet_url IS NOT NULL
ORDER BY heures_ecart DESC;
```
### Taux de succès des notifications
```sql
SELECT
COUNT(*) as total_virements,
COUNT(CASE WHEN notification_sent = true THEN 1 END) as notifications_envoyees,
COUNT(CASE WHEN notification_ok = true THEN 1 END) as notifications_ok,
ROUND(
100.0 * COUNT(CASE WHEN notification_ok = true THEN 1 END) /
NULLIF(COUNT(CASE WHEN notification_sent = true THEN 1 END), 0),
2
) as taux_succes_pct
FROM salary_transfers
WHERE notification_sent = true;
```
## Opérations de maintenance
### Mettre à jour le total_net d'un virement
```sql
-- Recalculer et mettre à jour le total net basé sur les contrats
UPDATE salary_transfers st
SET
total_net = (
SELECT SUM(CAST(c.net_amount AS NUMERIC))
FROM cddu_contracts c
WHERE c.org_id = st.org_id
AND c.payment_date IS NOT NULL
AND DATE_TRUNC('month', c.payment_date) = DATE_TRUNC('month', st.period_month)
),
updated_at = NOW()
WHERE st.id = 'VIREMENT_ID_ICI';
```
### Supprimer un virement (avec précaution)
```sql
-- Vérifier d'abord qu'il n'y a pas de dépendances
SELECT * FROM salary_transfers WHERE id = 'VIREMENT_ID_ICI';
-- Supprimer (ne pas oublier de supprimer le fichier S3 manuellement)
DELETE FROM salary_transfers WHERE id = 'VIREMENT_ID_ICI';
```
### Regénérer tous les PDFs manquants (via script)
```sql
-- Lister les virements sans PDF
SELECT
st.id,
st.org_id,
st.period_month,
o.name
FROM salary_transfers st
LEFT JOIN organizations o ON o.id = st.org_id
WHERE st.callsheet_url IS NULL
ORDER BY st.deadline ASC;
```
### Nettoyer les URLs S3 obsolètes
```sql
-- Lister les virements avec URLs S3 (pour vérification manuelle)
SELECT
id,
period_label,
callsheet_url,
updated_at
FROM salary_transfers
WHERE callsheet_url IS NOT NULL
ORDER BY updated_at DESC;
```
## Vérifications de cohérence
### Virements avec total_net incohérent
```sql
-- Compare le total_net stocké avec le total calculé des contrats
SELECT
st.id,
st.period_label,
st.total_net as total_enregistre,
(
SELECT SUM(CAST(c.net_amount AS NUMERIC))
FROM cddu_contracts c
WHERE c.org_id = st.org_id
AND c.payment_date IS NOT NULL
AND DATE_TRUNC('month', c.payment_date) = DATE_TRUNC('month', st.period_month)
) as total_calcule,
ABS(
CAST(st.total_net AS NUMERIC) -
COALESCE((
SELECT SUM(CAST(c.net_amount AS NUMERIC))
FROM cddu_contracts c
WHERE c.org_id = st.org_id
AND c.payment_date IS NOT NULL
AND DATE_TRUNC('month', c.payment_date) = DATE_TRUNC('month', st.period_month)
), 0)
) as difference
FROM salary_transfers st
WHERE st.total_net IS NOT NULL
HAVING ABS(
CAST(st.total_net AS NUMERIC) -
COALESCE((
SELECT SUM(CAST(c.net_amount AS NUMERIC))
FROM cddu_contracts c
WHERE c.org_id = st.org_id
AND c.payment_date IS NOT NULL
AND DATE_TRUNC('month', c.payment_date) = DATE_TRUNC('month', st.period_month)
), 0)
) > 0.01
ORDER BY difference DESC;
```
### Virements pour des organisations inexistantes
```sql
SELECT st.*
FROM salary_transfers st
LEFT JOIN organizations o ON o.id = st.org_id
WHERE o.id IS NULL;
```
### Doublons de virements (même org, même période, même mode)
```sql
SELECT
org_id,
period_month,
mode,
COUNT(*) as nombre_doublons
FROM salary_transfers
GROUP BY org_id, period_month, mode
HAVING COUNT(*) > 1
ORDER BY nombre_doublons DESC;
```
## Requêtes d'analyse
### Évolution mensuelle du nombre de virements
```sql
SELECT
TO_CHAR(period_month, 'YYYY-MM') as mois,
COUNT(*) as nombre_virements,
COUNT(CASE WHEN callsheet_url IS NOT NULL THEN 1 END) as avec_pdf,
SUM(CAST(total_net AS NUMERIC)) as total_montant
FROM salary_transfers
WHERE period_month >= '2024-01-01'
GROUP BY TO_CHAR(period_month, 'YYYY-MM')
ORDER BY mois DESC;
```
### Top 10 des organisations par volume
```sql
SELECT
o.name as organisation,
COUNT(st.id) as nb_virements,
SUM(CAST(st.total_net AS NUMERIC)) as total_montant,
AVG(CAST(st.total_net AS NUMERIC)) as montant_moyen
FROM organizations o
LEFT JOIN salary_transfers st ON st.org_id = o.id
GROUP BY o.id, o.name
ORDER BY total_montant DESC
LIMIT 10;
```
### Analyse des deadlines
```sql
SELECT
EXTRACT(DOW FROM deadline) as jour_semaine, -- 0=Dimanche, 1=Lundi, ...
COUNT(*) as nombre_virements
FROM salary_transfers
GROUP BY EXTRACT(DOW FROM deadline)
ORDER BY jour_semaine;
```
## Scripts d'export
### Export CSV des virements
```sql
COPY (
SELECT
st.id,
o.name as organisation,
st.period_label,
st.period_month,
st.deadline,
st.mode,
st.total_net,
CASE WHEN st.callsheet_url IS NOT NULL THEN 'Oui' ELSE 'Non' END as pdf_genere,
CASE WHEN st.notification_sent THEN 'Oui' ELSE 'Non' END as notification_envoyee,
CASE WHEN st.notification_ok THEN 'Oui' ELSE 'Non' END as notification_ok,
st.client_wire_received_at,
st.notes,
st.created_at
FROM salary_transfers st
LEFT JOIN organizations o ON o.id = st.org_id
WHERE st.period_month >= '2025-01-01'
ORDER BY st.period_month DESC
) TO '/tmp/virements_export.csv' WITH CSV HEADER;
```
### Export des contrats pour un virement
```sql
COPY (
SELECT
c.employee_last_name as nom,
c.employee_first_name as prenom,
c.employee_iban as iban,
c.payment_date as date_paiement,
c.net_amount as montant_net,
c.gross_amount as montant_brut
FROM cddu_contracts c
WHERE c.org_id = 'ORG_ID_ICI'
AND c.payment_date IS NOT NULL
AND DATE_TRUNC('month', c.payment_date) = '2025-01-01'::date
ORDER BY c.employee_last_name, c.employee_first_name
) TO '/tmp/contrats_export.csv' WITH CSV HEADER;
```
## Notes importantes
1. **Remplacer les placeholders** :
- `'ORG_ID_ICI'` → ID réel de l'organisation
- `'VIREMENT_ID_ICI'` → ID réel du virement
- `'2025-01-01'` → Date de période réelle
2. **Performance** :
- Pour les grandes tables, ajouter des index :
```sql
CREATE INDEX idx_salary_transfers_org_id ON salary_transfers(org_id);
CREATE INDEX idx_salary_transfers_period_month ON salary_transfers(period_month);
CREATE INDEX idx_cddu_contracts_payment_date ON cddu_contracts(payment_date);
```
3. **Types de données** :
- `total_net`, `net_amount`, `gross_amount` peuvent être `TEXT` ou `NUMERIC`
- Utiliser `CAST(...AS NUMERIC)` pour les calculs
4. **Dates** :
- `period_month` est stocké comme `DATE` (1er du mois)
- `deadline` est une `DATE` complète
- `payment_date` est une `DATE` complète
5. **RLS (Row Level Security)** :
- Ces requêtes doivent être exécutées avec un compte ayant les permissions appropriées
- En production, utiliser le Service Role Key pour les opérations sensibles