Correctif bases spécifiques simulateur
This commit is contained in:
parent
2497a80f4d
commit
dd4e8d17b1
4 changed files with 525 additions and 81 deletions
|
|
@ -33,23 +33,63 @@ PostHog
|
|||
### Nom de l'événement
|
||||
`simulateur_calculation`
|
||||
|
||||
### Propriétés
|
||||
### Propriétés complètes
|
||||
| Propriété | Type | Description | Exemple |
|
||||
|-----------|------|-------------|---------|
|
||||
| **Paramètres du formulaire** ||||
|
||||
| `ccn_id` | string | ID de la CCN sélectionnée | `"1285"` |
|
||||
| `ccn_nom` | string | Nom complet de la CCN | `"1285 – Entreprises Artistiques & Culturelles (CCNEAC)"` |
|
||||
| `categorie` | string | Catégorie du salarié | `"artiste"` ou `"technicien"` |
|
||||
| `type_calcul` | string | Type de calcul | `"brut"`, `"net"` ou `"cost"` |
|
||||
| `montant` | number | Montant saisi | `2000` |
|
||||
| `timestamp` | string | Date/heure de l'événement | `"2025-10-15T14:30:00.000Z"` |
|
||||
| `statut` | string | Statut professionnel | `"non-cadre"` ou `"cadre"` |
|
||||
| `abattement_active` | boolean | Abattement pour frais pro activé | `true` ou `false` |
|
||||
| `abattement_profession` | string | Type de profession (si abattement) | `"drama"`, `"musique"` ou `""` |
|
||||
| **Cachets, heures et dates** ||||
|
||||
| `cachets` | number | Nombre de cachets | `10` |
|
||||
| `heures` | number | Nombre d'heures | `35` |
|
||||
| `dates_travail` | array | Liste des dates de travail | `["2025-10-15", "2025-10-16"]` |
|
||||
| `nombre_jours` | number | Nombre de jours travaillés | `2` |
|
||||
| **Montant et type de calcul** ||||
|
||||
| `montant_saisi` | number | Montant saisi par l'utilisateur | `2000` |
|
||||
| `type_calcul` | string | Type de calcul effectué | `"brut"`, `"net"` ou `"cost"` |
|
||||
| **Résultats calculés** ||||
|
||||
| `resultat_brut` | number | Salaire brut calculé | `2000.00` |
|
||||
| `resultat_net` | number | Salaire net avant PAS calculé | `1580.50` |
|
||||
| `resultat_cost` | number | Coût total employeur calculé | `2456.80` |
|
||||
| **Métadonnées** ||||
|
||||
| `plafond_urssaf` | number | Plafond URSSAF appliqué | `3864` |
|
||||
| `timestamp` | string | Date/heure de la simulation | `"2025-10-16T14:30:00.000Z"` |
|
||||
|
||||
### Exemple d'événement
|
||||
### Exemple d'événement complet
|
||||
```javascript
|
||||
{
|
||||
event: 'simulateur_calculation',
|
||||
properties: {
|
||||
// Paramètres du formulaire
|
||||
ccn_id: '1285',
|
||||
ccn_nom: '1285 – Entreprises Artistiques & Culturelles (CCNEAC)',
|
||||
categorie: 'artiste',
|
||||
statut: 'non-cadre',
|
||||
abattement_active: true,
|
||||
abattement_profession: 'drama',
|
||||
|
||||
// Cachets, heures et dates
|
||||
cachets: 10,
|
||||
heures: 35,
|
||||
dates_travail: ['2025-10-15', '2025-10-16', '2025-10-17'],
|
||||
nombre_jours: 3,
|
||||
|
||||
// Montant et type de calcul
|
||||
montant_saisi: 2000,
|
||||
type_calcul: 'brut',
|
||||
montant: 2000,
|
||||
timestamp: '2025-10-15T14:30:00.000Z'
|
||||
|
||||
// Résultats calculés
|
||||
resultat_brut: 2000.00,
|
||||
resultat_net: 1580.50,
|
||||
resultat_cost: 2456.80,
|
||||
|
||||
// Métadonnées
|
||||
plafond_urssaf: 3864,
|
||||
timestamp: '2025-10-16T14:30:00.000Z'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -57,17 +97,56 @@ PostHog
|
|||
## Fichiers modifiés
|
||||
|
||||
### 1. `/public/simulateur-embed.html`
|
||||
**Ligne 1120+** : Ajout de l'envoi du message via `postMessage` dans le gestionnaire du bouton "Calculer"
|
||||
**Ligne 1326+** : Ajout de l'envoi du message enrichi via `postMessage` dans le gestionnaire du bouton "Calculer"
|
||||
|
||||
```javascript
|
||||
// 📊 Envoyer un événement PostHog via postMessage au parent
|
||||
// 📊 Envoyer un événement PostHog enrichi via postMessage au parent
|
||||
try {
|
||||
// Récupération de toutes les données du formulaire
|
||||
const ccnElement = document.getElementById('conventionSelect');
|
||||
const ccnValue = ccnElement?.value || '';
|
||||
const ccnText = ccnElement?.options[ccnElement.selectedIndex]?.text || '';
|
||||
|
||||
const categorieValue = isTechnicien() ? 'technicien' : 'artiste';
|
||||
const statutValue = document.getElementById('statutSelect')?.value || 'non-cadre';
|
||||
|
||||
// Abattement
|
||||
const abattementChecked = document.querySelector('input[name="abattement"]:checked')?.value || 'non';
|
||||
const professionValue = document.getElementById('professionSelect')?.value || '';
|
||||
|
||||
// Dates
|
||||
const datesInput = document.getElementById('datesInput')?.value || '';
|
||||
const datesArray = datesInput ? datesInput.split(',').map(d => d.trim()).filter(Boolean) : [];
|
||||
|
||||
window.parent.postMessage({
|
||||
type: 'simulateur_calculation',
|
||||
data: {
|
||||
categorie: isTechnicien() ? 'technicien' : 'artiste',
|
||||
type: document.querySelector('input[name="type"]:checked').value,
|
||||
montant: montant
|
||||
// Paramètres du formulaire
|
||||
ccn_id: ccnValue,
|
||||
ccn_nom: ccnText,
|
||||
categorie: categorieValue,
|
||||
statut: statutValue,
|
||||
abattement_active: abattementChecked === 'oui',
|
||||
abattement_profession: professionValue,
|
||||
|
||||
// Cachets, heures, dates
|
||||
cachets: cachets,
|
||||
heures: heures,
|
||||
dates_travail: datesArray,
|
||||
nombre_jours: datesArray.length,
|
||||
|
||||
// Montant saisi et type
|
||||
montant_saisi: montant,
|
||||
type_calcul: type,
|
||||
|
||||
// Résultats calculés
|
||||
resultat_brut: parseFloat(brut.toFixed(2)),
|
||||
resultat_net: parseFloat(net.toFixed(2)),
|
||||
resultat_cost: parseFloat(costEmployer.toFixed(2)),
|
||||
|
||||
// Métadonnées
|
||||
plafond_urssaf: plafondUrssaf,
|
||||
timestamp: new Date().toISOString()
|
||||
}
|
||||
}, '*');
|
||||
} catch (e) {
|
||||
|
|
@ -76,29 +155,50 @@ try {
|
|||
```
|
||||
|
||||
### 2. `/app/(app)/simulateur/page.tsx`
|
||||
**Lignes 1-35** : Ajout du hook PostHog et de l'écouteur de messages
|
||||
**Lignes 12-50** : Mise à jour du hook PostHog pour capturer toutes les propriétés enrichies
|
||||
|
||||
```typescript
|
||||
import { usePostHog } from 'posthog-js/react';
|
||||
|
||||
export default function SimulateurPage() {
|
||||
const posthog = usePostHog();
|
||||
|
||||
// 📊 Écouter les messages de l'iframe pour tracker les calculs
|
||||
useEffect(() => {
|
||||
useEffect(() => {
|
||||
const handleMessage = (event: MessageEvent) => {
|
||||
if (event.data?.type === 'simulateur_calculation') {
|
||||
const { categorie, type, montant } = event.data.data;
|
||||
const data = event.data.data;
|
||||
|
||||
posthog?.capture('simulateur_calculation', {
|
||||
categorie,
|
||||
type_calcul: type,
|
||||
montant,
|
||||
timestamp: new Date().toISOString()
|
||||
// Paramètres du formulaire
|
||||
ccn_id: data.ccn_id,
|
||||
ccn_nom: data.ccn_nom,
|
||||
categorie: data.categorie,
|
||||
statut: data.statut,
|
||||
abattement_active: data.abattement_active,
|
||||
abattement_profession: data.abattement_profession,
|
||||
|
||||
// Cachets, heures, dates
|
||||
cachets: data.cachets,
|
||||
heures: data.heures,
|
||||
dates_travail: data.dates_travail,
|
||||
nombre_jours: data.nombre_jours,
|
||||
|
||||
// Montant saisi et type
|
||||
montant_saisi: data.montant_saisi,
|
||||
type_calcul: data.type_calcul,
|
||||
|
||||
// Résultats calculés
|
||||
resultat_brut: data.resultat_brut,
|
||||
resultat_net: data.resultat_net,
|
||||
resultat_cost: data.resultat_cost,
|
||||
|
||||
// Métadonnées
|
||||
plafond_urssaf: data.plafond_urssaf,
|
||||
timestamp: data.timestamp
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('message', handleMessage);
|
||||
return () => window.removeEventListener('message', handleMessage);
|
||||
}, [posthog]);
|
||||
```
|
||||
|
||||
window.addEventListener('message', handleMessage);
|
||||
return () => window.removeEventListener('message', handleMessage);
|
||||
}, [posthog]);
|
||||
|
|
@ -136,7 +236,17 @@ Targeting:
|
|||
### Dans la console du navigateur
|
||||
Lors d'un clic sur "Calculer", vous devriez voir :
|
||||
```
|
||||
📊 PostHog: Événement simulateur_calculation envoyé { categorie: 'artiste', type: 'brut', montant: 2000 }
|
||||
📊 PostHog: Événement simulateur_calculation enrichi envoyé {
|
||||
categorie: 'artiste',
|
||||
ccn: '1285 – Entreprises Artistiques & Culturelles (CCNEAC)',
|
||||
type_calcul: 'brut',
|
||||
montant_saisi: 2000,
|
||||
resultats: {
|
||||
brut: 2000,
|
||||
net: 1580.5,
|
||||
cost: 2456.8
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Dans PostHog
|
||||
|
|
@ -168,27 +278,81 @@ Lors d'un clic sur "Calculer", vous devriez voir :
|
|||
|
||||
## Évolutions futures
|
||||
|
||||
### Idées d'améliorations
|
||||
- Tracker également les modifications de paramètres (CCN, catégorie, etc.)
|
||||
- Ajouter le temps passé sur le simulateur
|
||||
- Tracker les exports PDF (si fonctionnalité ajoutée)
|
||||
- Ajouter des événements pour les erreurs de validation
|
||||
### Analyses possibles avec les données enrichies
|
||||
Avec toutes ces propriétés, vous pouvez maintenant analyser :
|
||||
|
||||
### Autres métriques possibles
|
||||
```javascript
|
||||
// Exemple d'événements supplémentaires
|
||||
posthog.capture('simulateur_ccn_changed', { ccn: '1285' });
|
||||
posthog.capture('simulateur_export_pdf', { format: 'pdf' });
|
||||
posthog.capture('simulateur_error', { error_type: 'invalid_amount' });
|
||||
- **Par CCN** : Quelle convention collective est la plus utilisée ?
|
||||
- **Par catégorie** : Artiste vs Technicien - ratio d'utilisation
|
||||
- **Par type de calcul** : Les utilisateurs partent-ils plutôt du brut, net ou coût ?
|
||||
- **Abattement** : Combien d'utilisateurs utilisent l'abattement pour frais professionnels ?
|
||||
- **Montants moyens** : Salaire brut moyen, net moyen, coût moyen par CCN
|
||||
- **Heures et cachets** : Patterns d'utilisation (nombre moyen de cachets, heures par simulation)
|
||||
- **Périodes** : Analyse temporelle des simulations (jours, semaines, mois)
|
||||
|
||||
### Exemples de requêtes PostHog
|
||||
|
||||
**Salaire brut moyen par CCN** :
|
||||
```sql
|
||||
SELECT
|
||||
ccn_nom,
|
||||
AVG(resultat_brut) as brut_moyen,
|
||||
COUNT(*) as nb_simulations
|
||||
FROM events
|
||||
WHERE event = 'simulateur_calculation'
|
||||
GROUP BY ccn_nom
|
||||
ORDER BY nb_simulations DESC
|
||||
```
|
||||
|
||||
**Répartition Artiste vs Technicien** :
|
||||
```sql
|
||||
SELECT
|
||||
categorie,
|
||||
COUNT(*) as total,
|
||||
AVG(resultat_brut) as brut_moyen
|
||||
FROM events
|
||||
WHERE event = 'simulateur_calculation'
|
||||
GROUP BY categorie
|
||||
```
|
||||
|
||||
**Taux d'utilisation de l'abattement** :
|
||||
```sql
|
||||
SELECT
|
||||
abattement_active,
|
||||
COUNT(*) as total,
|
||||
(COUNT(*) * 100.0 / SUM(COUNT(*)) OVER ()) as pourcentage
|
||||
FROM events
|
||||
WHERE event = 'simulateur_calculation'
|
||||
AND categorie = 'artiste'
|
||||
GROUP BY abattement_active
|
||||
```
|
||||
|
||||
### Idées d'améliorations futures
|
||||
- Tracker les exports PDF (si fonctionnalité ajoutée)
|
||||
- Ajouter le temps passé sur le simulateur
|
||||
- Tracker les erreurs de validation
|
||||
- Capturer le device type (mobile/desktop)
|
||||
- Ajouter des événements pour les modifications de paramètres sans calcul
|
||||
|
||||
## Notes techniques
|
||||
|
||||
- ✅ Le simulateur est maintenant actif dans la sidebar
|
||||
- ✅ Les événements PostHog sont trackés pour analyse
|
||||
- ✅ Les événements PostHog sont trackés avec **toutes les données de simulation**
|
||||
- ✅ Compatible avec les surveys PostHog
|
||||
- ✅ Aucune dépendance externe supplémentaire requise
|
||||
- ✅ Fonctionne en mode production et développement
|
||||
- ✅ **Données enrichies** : CCN, catégorie, statut, abattement, cachets, heures, dates, montants et résultats complets
|
||||
|
||||
## Données capturées (résumé)
|
||||
| Catégorie | Données |
|
||||
|-----------|---------|
|
||||
| **CCN** | ID et nom complet de la convention |
|
||||
| **Profil** | Catégorie (artiste/technicien), statut (cadre/non-cadre) |
|
||||
| **Abattement** | Activation et type de profession |
|
||||
| **Volumes** | Cachets, heures, dates de travail, nombre de jours |
|
||||
| **Calcul** | Montant saisi, type de calcul (brut/net/cost) |
|
||||
| **Résultats** | Brut, Net avant PAS, Coût Total Employeur |
|
||||
| **Contexte** | Plafond URSSAF, timestamp |
|
||||
|
||||
## Date de mise en place
|
||||
15 octobre 2025
|
||||
- **Première version** : 15 octobre 2025 (tracking basique)
|
||||
- **Version enrichie** : 16 octobre 2025 (toutes les données)
|
||||
|
|
|
|||
|
|
@ -14,17 +14,49 @@ export default function SimulateurPage() {
|
|||
const handleMessage = (event: MessageEvent) => {
|
||||
// Vérifier que le message vient de notre iframe
|
||||
if (event.data?.type === 'simulateur_calculation') {
|
||||
const { categorie, type, montant } = event.data.data;
|
||||
const data = event.data.data;
|
||||
|
||||
// Envoyer l'événement à PostHog
|
||||
// Envoyer l'événement enrichi à PostHog
|
||||
posthog?.capture('simulateur_calculation', {
|
||||
categorie,
|
||||
type_calcul: type,
|
||||
montant,
|
||||
timestamp: new Date().toISOString()
|
||||
// Paramètres du formulaire
|
||||
ccn_id: data.ccn_id,
|
||||
ccn_nom: data.ccn_nom,
|
||||
categorie: data.categorie,
|
||||
statut: data.statut,
|
||||
abattement_active: data.abattement_active,
|
||||
abattement_profession: data.abattement_profession,
|
||||
|
||||
// Cachets, heures, dates
|
||||
cachets: data.cachets,
|
||||
heures: data.heures,
|
||||
dates_travail: data.dates_travail,
|
||||
nombre_jours: data.nombre_jours,
|
||||
|
||||
// Montant saisi et type
|
||||
montant_saisi: data.montant_saisi,
|
||||
type_calcul: data.type_calcul,
|
||||
|
||||
// Résultats calculés
|
||||
resultat_brut: data.resultat_brut,
|
||||
resultat_net: data.resultat_net,
|
||||
resultat_cost: data.resultat_cost,
|
||||
|
||||
// Métadonnées
|
||||
plafond_urssaf: data.plafond_urssaf,
|
||||
timestamp: data.timestamp
|
||||
});
|
||||
|
||||
console.log('📊 PostHog: Événement simulateur_calculation envoyé', { categorie, type, montant });
|
||||
console.log('📊 PostHog: Événement simulateur_calculation enrichi envoyé', {
|
||||
categorie: data.categorie,
|
||||
ccn: data.ccn_nom,
|
||||
type_calcul: data.type_calcul,
|
||||
montant_saisi: data.montant_saisi,
|
||||
resultats: {
|
||||
brut: data.resultat_brut,
|
||||
net: data.resultat_net,
|
||||
cost: data.resultat_cost
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -458,7 +458,7 @@ export default function Sidebar({ clientInfo, isStaff = false, mobile = false, o
|
|||
<DisabledMenuItem
|
||||
icon={Scale}
|
||||
label="Minima CCN"
|
||||
tooltipMessage="Le simulateur et les minima seront de retour dans quelques jours."
|
||||
tooltipMessage="Les minima CCN seront de retour dans quelques jours."
|
||||
/>
|
||||
<Link href="/simulateur" onClick={() => onNavigate && onNavigate()} className={`block px-3 py-2 rounded-xl text-sm transition truncate ${
|
||||
isActivePath(pathname, "/simulateur") ? "bg-gradient-to-r from-indigo-200 via-purple-200 to-pink-200 text-slate-700 font-semibold" : "hover:bg-slate-50"
|
||||
|
|
|
|||
|
|
@ -141,6 +141,9 @@
|
|||
<button id="calcBtn">Calculer</button>
|
||||
</div>
|
||||
|
||||
<!-- Alerts -->
|
||||
<div id="alerts"></div>
|
||||
|
||||
<!-- Résultats -->
|
||||
<div class="result">
|
||||
<h4>Résultat de la simulation</h4>
|
||||
|
|
@ -314,6 +317,27 @@ function getPlafondUrssaf() {
|
|||
return PMSS * (diffDays / daysInMonth);
|
||||
}
|
||||
|
||||
// ===== IRC Tranche 1 Max (T1 cap, AGIRC-ARRCO) =====
|
||||
function getIrcT1Max(){
|
||||
// Mode intermittent ajusté : 12 × PMSS × (span / (daysInMonth × 0.95)), plafonné à 12 × PMSS
|
||||
const dates = getSelectedDatesArray();
|
||||
if (!dates.length) return 0;
|
||||
const first = dates[0], last = dates[dates.length-1];
|
||||
const diffDays = Math.round((last - first) / (1000*60*60*24)) + 1;
|
||||
const y = first.getFullYear();
|
||||
const m = first.getMonth();
|
||||
const daysInMonth = new Date(y, m + 1, 0).getDate();
|
||||
const adjusted = PMSS * 12 * (diffDays / (daysInMonth * 0.95));
|
||||
return Math.min(adjusted, PMSS * 12);
|
||||
}
|
||||
// ===== Helper: split IRC base between T1 and T2 (avant abattement) =====
|
||||
function getIrcBasesBeforeAbattement(brut){
|
||||
const t1Max = getIrcT1Max();
|
||||
const baseT1 = Math.min(brut, Math.max(0, t1Max));
|
||||
const baseT2 = Math.max(0, brut - Math.max(0, t1Max));
|
||||
return { baseT1, baseT2 };
|
||||
}
|
||||
|
||||
/* ===== Compléments maladie / AF jour par jour ===== */
|
||||
const HOURS_PER_CACHET_FOR_COMPLEMENT = 8; // pour les seuils
|
||||
const HOURS_PER_DAY_FOR_COMPLEMENT_IF_CACHET = 6.7; // forfait si cachet ce jour
|
||||
|
|
@ -346,15 +370,40 @@ function getAssietteChomageMaxPourMoisCourant() {
|
|||
const m = dates[0].getMonth();
|
||||
const daysInMonth = new Date(y, m + 1, 0).getDate();
|
||||
|
||||
// Ajout : nombre de cachets et nbJoursChomage (max des deux)
|
||||
const cachetsRaw = parseInt(document.getElementById('cachetsInput')?.value, 10);
|
||||
const cachets = (isNaN(cachetsRaw) || cachetsRaw < 0 || isTechnicien()) ? 0 : cachetsRaw;
|
||||
const nbJoursChomage = Math.max(nbJours, cachets);
|
||||
|
||||
// Règle attendue :
|
||||
// - < 5 jours : plafond chômage = 4 × plafond URSSAF de la période
|
||||
// - ≥ 5 jours : plafond chômage = 4 × PMSS × (nbJours / daysInMonth)
|
||||
// - ≥ 5 jours : plafond chômage = 4 × PMSS × (span en jours / daysInMonth)
|
||||
if (diffDays < 5) {
|
||||
const plafUrssaf = getPlafondUrssaf();
|
||||
return 4 * plafUrssaf;
|
||||
}
|
||||
|
||||
return 4 * PMSS * (nbJours / daysInMonth);
|
||||
return 4 * PMSS * (diffDays / daysInMonth);
|
||||
}
|
||||
|
||||
// === Debug panel helper for chômage assiette max
|
||||
function getChomageDebugInfo() {
|
||||
const dates = getSelectedDatesArray();
|
||||
if (!dates.length) return null;
|
||||
const first = dates[0], last = dates[dates.length-1];
|
||||
const diffDays = Math.round((last - first) / (1000*60*60*24)) + 1;
|
||||
const nbJours = dates.length;
|
||||
const y = dates[0].getFullYear();
|
||||
const m = dates[0].getMonth();
|
||||
const daysInMonth = new Date(y, m + 1, 0).getDate();
|
||||
const cachetsRaw = parseInt(document.getElementById('cachetsInput')?.value, 10);
|
||||
const cachets = (isNaN(cachetsRaw) || cachetsRaw < 0 || isTechnicien()) ? 0 : cachetsRaw;
|
||||
const nbJoursChomage = Math.max(nbJours, cachets);
|
||||
const plafondShort = 4 * getPlafondUrssaf();
|
||||
// Use diffDays for plafondLong as per new rule
|
||||
const plafondLong = 4 * PMSS * (diffDays / daysInMonth);
|
||||
const assiette = (diffDays < 5) ? plafondShort : plafondLong;
|
||||
return { diffDays, nbJours, cachets, nbJoursChomage, daysInMonth, assiette, branch: (diffDays < 5) ? '<5 jours' : '>=5 jours', usedRatioDays: (diffDays < 5) ? null : diffDays };
|
||||
}
|
||||
|
||||
// Taux complémentaires selon catégorie
|
||||
|
|
@ -478,6 +527,9 @@ function baseLibelle(code, cat){
|
|||
"ags": "AGS intermittent",
|
||||
"retraite_t1": isTech ? "Retraite non-cadre Int. T1" : "Retraite artiste Tranche 1",
|
||||
"ceg_t1": isTech ? "CEG non-cadre Int. T1" : "CEG artiste intermittent Tranche 1",
|
||||
"retraite_t2": isTech ? "Retraite non-cadre Int. T2" : "Retraite artiste Tranche 2",
|
||||
"ceg_t2": isTech ? "CEG non-cadre Int. T2" : "CEG artiste intermittent Tranche 2",
|
||||
"cet": isTech ? "CET non-cadre Int." : "CET artiste intermittent",
|
||||
"prevoyance_ta": isTech ? "Prévoyance non-cadre Int. TA" : "Prévoyance non-cadre Artiste Int. TA",
|
||||
"conges_spectacles": "Congés spectacles",
|
||||
"medecine_t1": isTech ? "Médecine du travail int. T1" : "Médecine du travail int.",
|
||||
|
|
@ -514,19 +566,23 @@ function getProfileRates(cat){
|
|||
}
|
||||
};
|
||||
}
|
||||
// Artiste = base existante
|
||||
// Artiste
|
||||
return {
|
||||
sal: {
|
||||
contrib_solidarite: 0, maladie: 0, vieillesse: 0.28, alloc_fam: 0, at: 0,
|
||||
vieillesse_ta: 4.83, fnal_plaf: 0, maj_chomage: 0, chomage: 2.4, ags: 0,
|
||||
ceg_t1: 0.86, retraite_t1: 4.44, prevoyance_ta: 0.12, conges_spectacles: 0,
|
||||
ceg_t1: 0.86, retraite_t1: 4.44, retraite_t2: 10.79, ceg_t2: 1.08,
|
||||
cet: 0.14,
|
||||
prevoyance_ta: 0.12, conges_spectacles: 0,
|
||||
casc_svp: 0, fnas: 0, fcap: 0, medecine_t1: 0, cfpc_conv: 0, cfp_ta: 0,
|
||||
paritarisme: 0, csg_deductible: 6.8, csg_imposable: 2.4, rds: 0.5
|
||||
},
|
||||
emp: {
|
||||
contrib_solidarite: 0.3, maladie: 4.90, vieillesse: 1.41, alloc_fam: 2.42, at: 1.18,
|
||||
vieillesse_ta: 5.99, fnal_plaf: 0.07, maj_chomage: 0.5, chomage: 9.0, ags: 0.25,
|
||||
ceg_t1: 1.29, retraite_t1: 4.45, prevoyance_ta: 1.04, conges_spectacles: 15.5,
|
||||
ceg_t1: 1.29, retraite_t1: 4.45, retraite_t2: 10.80, ceg_t2: 1.62,
|
||||
cet: 0.21,
|
||||
prevoyance_ta: 1.04, conges_spectacles: 15.5,
|
||||
casc_svp: 0.4, fnas: 1.45, fcap: 0.25, medecine_t1: 0.32, cfpc_conv: 0.1,
|
||||
cfp_ta: 2.0, paritarisme: 0.016, csg_deductible: 0, csg_imposable: 0, rds: 0
|
||||
}
|
||||
|
|
@ -538,13 +594,20 @@ function getCotisations(){
|
|||
const cc = document.getElementById('conventionSelect')?.value;
|
||||
const rates = getProfileRates(cat);
|
||||
|
||||
const codes = [
|
||||
let codes = [
|
||||
"contrib_solidarite","maladie","vieillesse","alloc_fam","at",
|
||||
"vieillesse_ta","fnal_plaf","maj_chomage","chomage","ags",
|
||||
"retraite_t1","ceg_t1","prevoyance_ta","conges_spectacles",
|
||||
"medecine_t1","cfpc_conv","cfp_ta","paritarisme",
|
||||
"csg_deductible","csg_imposable","rds"
|
||||
];
|
||||
// Pour ARTISTE, insérer T2 après ceg_t1, puis CET
|
||||
if (cat === 'artiste') {
|
||||
const idx = codes.indexOf("ceg_t1");
|
||||
if (idx !== -1) {
|
||||
codes.splice(idx + 1, 0, "retraite_t2","ceg_t2","cet");
|
||||
}
|
||||
}
|
||||
if (cc === "3090") codes.push("casc_svp"); // CCNSVP
|
||||
if (cc === "1285") { codes.push("fnas","fcap"); } // CCNEAC
|
||||
|
||||
|
|
@ -574,6 +637,47 @@ function getCotisations(){
|
|||
return { sal, emp, cat };
|
||||
}
|
||||
|
||||
/* ===== Somme des contributions employeur incluses dans l'assiette CSG/CRDS ===== */
|
||||
function employerContribsForCSGBase(brut){
|
||||
const { emp } = getCotisations();
|
||||
const dates = getSelectedDatesArray();
|
||||
if (!dates.length) return 0;
|
||||
|
||||
const prevBase = getPrevoyanceBase(brut);
|
||||
const assietteChomageMax = getAssietteChomageMaxPourMoisCourant();
|
||||
|
||||
let total = 0;
|
||||
for (const e of emp){
|
||||
let amount = 0;
|
||||
switch (e.code){
|
||||
// ✅ Inclure : chômage / maj. chômage / AGS sur leur assiette plafonnée
|
||||
case 'chomage':
|
||||
case 'maj_chomage':
|
||||
case 'ags': {
|
||||
const base = Math.min(brut, assietteChomageMax);
|
||||
amount = base * (e.taux / 100);
|
||||
break;
|
||||
}
|
||||
// ✅ Inclure : prévoyance TA (sur base plafonnée jour)
|
||||
case 'prevoyance_ta':
|
||||
amount = prevBase * (e.taux / 100);
|
||||
break;
|
||||
// ✅ Inclure : contributions conventionnelles simples
|
||||
case 'cfpc_conv':
|
||||
case 'paritarisme':
|
||||
case 'apec':
|
||||
amount = brut * (e.taux / 100);
|
||||
break;
|
||||
|
||||
// ❌ Exclure : IRC (AGIRC-ARRCO) T1/T2 (retraite & CEG), FNAL, maladie, vieillesse, AF, AT, etc.
|
||||
default:
|
||||
break;
|
||||
}
|
||||
total += amount;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
/* ===== Prévoyance employeur ===== */
|
||||
function prevoyanceEmployerAmount(brut) {
|
||||
const { emp } = getCotisations();
|
||||
|
|
@ -591,7 +695,8 @@ function calculateNet(brut, plafondUrssaf) {
|
|||
const nonAbattementCodes = [
|
||||
"maj_chomage","chomage","ags","conges_spectacles",
|
||||
"csg_deductible","csg_imposable","rds",
|
||||
"prevoyance_ta" // ✅ ne pas abattre la prévoyance
|
||||
"prevoyance_ta",
|
||||
"cet"
|
||||
];
|
||||
|
||||
const assietteChomageMax = getAssietteChomageMaxPourMoisCourant();
|
||||
|
|
@ -610,7 +715,17 @@ const nonAbattementCodes = [
|
|||
} else if (c.code === "prevoyance_ta" || (c.libelle && c.libelle.includes("Prévoyance"))) {
|
||||
base = getPrevoyanceBase(brut);
|
||||
} else if (["csg_deductible","csg_imposable","rds"].includes(c.code)) {
|
||||
base = (brut + prevEmp) * 0.9825;
|
||||
const incEmp = employerContribsForCSGBase(brut);
|
||||
base = (brut + incEmp) * 0.9825;
|
||||
} else if (c.code === "retraite_t1" || c.code === "ceg_t1") {
|
||||
const { baseT1 } = getIrcBasesBeforeAbattement(brut);
|
||||
base = baseT1; // abattement éventuel appliqué plus bas si autorisé
|
||||
} else if (c.code === "retraite_t2" || c.code === "ceg_t2") {
|
||||
const { baseT2 } = getIrcBasesBeforeAbattement(brut);
|
||||
base = baseT2; // abattement éventuel appliqué plus bas si autorisé
|
||||
} else if (c.code === "cet") {
|
||||
base = brut;
|
||||
appliedAbattement = true;
|
||||
} else {
|
||||
base = brut;
|
||||
}
|
||||
|
|
@ -632,7 +747,8 @@ function computeEmployerCharges(brut, cachets, heures, plafondUrssaf) {
|
|||
const nonAbattementCodes = [
|
||||
"maj_chomage","chomage","ags","conges_spectacles",
|
||||
"csg_deductible","csg_imposable","rds",
|
||||
"prevoyance_ta" // ✅ ne pas abattre la prévoyance
|
||||
"prevoyance_ta",
|
||||
"cet"
|
||||
];
|
||||
|
||||
// Fusionne codes salariaux/patronaux pour calculer la part patronale
|
||||
|
|
@ -664,7 +780,17 @@ const nonAbattementCodes = [
|
|||
} else if (c.code === "prevoyance_ta" || (c.libelle && c.libelle.includes("Prévoyance"))) {
|
||||
base = getPrevoyanceBase(brut);
|
||||
} else if (["csg_deductible","csg_imposable","rds"].includes(c.code)) {
|
||||
base = (brut + prevEmp) * 0.9825;
|
||||
const incEmp = employerContribsForCSGBase(brut);
|
||||
base = (brut + incEmp) * 0.9825;
|
||||
} else if (c.code === "retraite_t1" || c.code === "ceg_t1") {
|
||||
const { baseT1 } = getIrcBasesBeforeAbattement(brut);
|
||||
base = baseT1; // abattement éventuel appliqué plus bas si autorisé
|
||||
} else if (c.code === "retraite_t2" || c.code === "ceg_t2") {
|
||||
const { baseT2 } = getIrcBasesBeforeAbattement(brut);
|
||||
base = baseT2; // abattement éventuel appliqué plus bas si autorisé
|
||||
} else if (c.code === "cet") {
|
||||
base = brut;
|
||||
appliedAbattement = true;
|
||||
} else {
|
||||
base = brut;
|
||||
}
|
||||
|
|
@ -864,7 +990,8 @@ function generateDetailTable(brut, cachets, heures, plafondUrssaf) {
|
|||
const nonAbattementCodes = [
|
||||
"maj_chomage","chomage","ags","conges_spectacles",
|
||||
"csg_deductible","csg_imposable","rds",
|
||||
"prevoyance_ta" // ✅ ne pas abattre la prévoyance
|
||||
"prevoyance_ta",
|
||||
"cet"
|
||||
];
|
||||
const { sal, emp, cat } = getCotisations();
|
||||
const assietteChomageMax = getAssietteChomageMaxPourMoisCourant();
|
||||
|
|
@ -907,7 +1034,17 @@ const nonAbattementCodes = [
|
|||
} else if (c.code === "prevoyance_ta" || (c.libelle && c.libelle.includes("Prévoyance"))) {
|
||||
base = getPrevoyanceBase(brut);
|
||||
} else if (["csg_deductible","csg_imposable","rds"].includes(c.code)) {
|
||||
base = (brut + prevEmp) * 0.9825;
|
||||
const incEmp = employerContribsForCSGBase(brut);
|
||||
base = (brut + incEmp) * 0.9825;
|
||||
} else if (c.code === "retraite_t1" || c.code === "ceg_t1") {
|
||||
const { baseT1 } = getIrcBasesBeforeAbattement(brut);
|
||||
base = baseT1; // abattement éventuel appliqué plus bas si autorisé
|
||||
} else if (c.code === "retraite_t2" || c.code === "ceg_t2") {
|
||||
const { baseT2 } = getIrcBasesBeforeAbattement(brut);
|
||||
base = baseT2; // abattement éventuel appliqué plus bas si autorisé
|
||||
} else if (c.code === "cet") {
|
||||
base = brut;
|
||||
appliedAbattement = true;
|
||||
} else {
|
||||
base = brut;
|
||||
}
|
||||
|
|
@ -983,7 +1120,8 @@ function buildContributionsStructured(brut, cachets, heures, plafondUrssaf){
|
|||
const nonAbattementCodes = [
|
||||
"maj_chomage","chomage","ags","conges_spectacles",
|
||||
"csg_deductible","csg_imposable","rds",
|
||||
"prevoyance_ta" // ✅ ne pas abattre la prévoyance
|
||||
"prevoyance_ta",
|
||||
"cet"
|
||||
];
|
||||
const { sal, emp, cat } = getCotisations();
|
||||
const assietteChomageMax = getAssietteChomageMaxPourMoisCourant();
|
||||
|
|
@ -1100,7 +1238,17 @@ if (fillonTotal > 0) {
|
|||
} else if (c.code === "prevoyance_ta" || (c.libelle && c.libelle.includes("Prévoyance"))) {
|
||||
base = getPrevoyanceBase(brut);
|
||||
} else if (["csg_deductible","csg_imposable","rds"].includes(c.code)) {
|
||||
base = (brut + prevoyanceEmployerAmount(brut)) * 0.9825;
|
||||
const incEmp = employerContribsForCSGBase(brut);
|
||||
base = (brut + incEmp) * 0.9825;
|
||||
} else if (c.code === "retraite_t1" || c.code === "ceg_t1") {
|
||||
const { baseT1 } = getIrcBasesBeforeAbattement(brut);
|
||||
base = baseT1; // abattement éventuel appliqué plus bas si autorisé
|
||||
} else if (c.code === "retraite_t2" || c.code === "ceg_t2") {
|
||||
const { baseT2 } = getIrcBasesBeforeAbattement(brut);
|
||||
base = baseT2; // abattement éventuel appliqué plus bas si autorisé
|
||||
} else if (c.code === "cet") {
|
||||
base = brut;
|
||||
appliedAbattement = true;
|
||||
} else {
|
||||
base = brut;
|
||||
}
|
||||
|
|
@ -1159,20 +1307,6 @@ document.getElementById('calcBtn').addEventListener('click', function() {
|
|||
return;
|
||||
}
|
||||
|
||||
// 📊 Envoyer un événement PostHog via postMessage au parent
|
||||
try {
|
||||
window.parent.postMessage({
|
||||
type: 'simulateur_calculation',
|
||||
data: {
|
||||
categorie: isTechnicien() ? 'technicien' : 'artiste',
|
||||
type: document.querySelector('input[name="type"]:checked').value,
|
||||
montant: montant
|
||||
}
|
||||
}, '*');
|
||||
} catch (e) {
|
||||
console.error('Erreur lors de l\'envoi de l\'événement PostHog:', e);
|
||||
}
|
||||
|
||||
const plafondUrssaf = getPlafondUrssaf();
|
||||
const type = document.querySelector('input[name="type"]:checked').value;
|
||||
|
||||
|
|
@ -1191,6 +1325,119 @@ document.getElementById('calcBtn').addEventListener('click', function() {
|
|||
({ net, costEmployer } = calculateSalaries(brut, cachets, heures, plafondUrssaf));
|
||||
}
|
||||
|
||||
// 📊 Envoyer un événement PostHog enrichi via postMessage au parent
|
||||
try {
|
||||
// Récupération de toutes les données du formulaire
|
||||
const ccnElement = document.getElementById('conventionSelect');
|
||||
const ccnValue = ccnElement?.value || '';
|
||||
const ccnText = ccnElement?.options[ccnElement.selectedIndex]?.text || '';
|
||||
|
||||
const categorieValue = isTechnicien() ? 'technicien' : 'artiste';
|
||||
const statutValue = document.getElementById('statutSelect')?.value || 'non-cadre';
|
||||
|
||||
// Abattement
|
||||
const abattementChecked = document.querySelector('input[name="abattement"]:checked')?.value || 'non';
|
||||
const professionValue = document.getElementById('professionSelect')?.value || '';
|
||||
|
||||
// Dates
|
||||
const datesInput = document.getElementById('datesInput')?.value || '';
|
||||
const datesArray = datesInput ? datesInput.split(',').map(d => d.trim()).filter(Boolean) : [];
|
||||
|
||||
window.parent.postMessage({
|
||||
type: 'simulateur_calculation',
|
||||
data: {
|
||||
// Paramètres du formulaire
|
||||
ccn_id: ccnValue,
|
||||
ccn_nom: ccnText,
|
||||
categorie: categorieValue,
|
||||
statut: statutValue,
|
||||
abattement_active: abattementChecked === 'oui',
|
||||
abattement_profession: professionValue,
|
||||
|
||||
// Cachets, heures, dates
|
||||
cachets: cachets,
|
||||
heures: heures,
|
||||
dates_travail: datesArray,
|
||||
nombre_jours: datesArray.length,
|
||||
|
||||
// Montant saisi et type
|
||||
montant_saisi: montant,
|
||||
type_calcul: type, // 'brut', 'net', ou 'cost'
|
||||
|
||||
// Résultats calculés
|
||||
resultat_brut: parseFloat(brut.toFixed(2)),
|
||||
resultat_net: parseFloat(net.toFixed(2)),
|
||||
resultat_cost: parseFloat(costEmployer.toFixed(2)),
|
||||
|
||||
// Métadonnées
|
||||
plafond_urssaf: plafondUrssaf,
|
||||
timestamp: new Date().toISOString()
|
||||
}
|
||||
}, '*');
|
||||
|
||||
console.log('📊 PostHog: Événement simulateur_calculation enrichi envoyé');
|
||||
} catch (e) {
|
||||
console.error('Erreur lors de l\'envoi de l\'événement PostHog:', e);
|
||||
}
|
||||
|
||||
// —— Alerts & hard stop on extreme simulations ——
|
||||
const alertsEl = document.getElementById('alerts');
|
||||
if (alertsEl) alertsEl.innerHTML = '';
|
||||
|
||||
// Hard stop > 100 000 €
|
||||
if (brut > 100000) {
|
||||
try {
|
||||
swal({
|
||||
title: "Montant très élevé",
|
||||
text: "Cette simulation dépasse le périmètre de l'outil. Merci de nous contacter pour confirmer ou valider la simulation.",
|
||||
icon: "warning",
|
||||
buttons: {
|
||||
cancel: "Fermer",
|
||||
support: {
|
||||
text: "Contacter le support",
|
||||
value: "support",
|
||||
closeModal: true
|
||||
}
|
||||
}
|
||||
}).then(value => {
|
||||
if (value === "support") window.location.href = "/support";
|
||||
});
|
||||
} catch(e){
|
||||
// Fallback simple si SweetAlert indisponible
|
||||
if (confirm("Montant très élevé. Ouvrir la page support ?")) window.location.href = "/support";
|
||||
}
|
||||
// Stop calculation rendering
|
||||
document.getElementById('result').innerHTML = '';
|
||||
document.getElementById('detailTable').innerHTML = '';
|
||||
return;
|
||||
}
|
||||
|
||||
// Alerte non bloquante > 10 000 €
|
||||
if (brut > 10000) {
|
||||
if (alertsEl) {
|
||||
alertsEl.innerHTML = `
|
||||
<div class="alert alert-warning" role="alert" style="margin:16px 0">
|
||||
<div style="display:flex; align-items:center; justify-content:space-between; gap:12px;">
|
||||
<div>
|
||||
<strong>Simulation extrême :</strong> le montant saisi est très élevé.\n
|
||||
Nous vous invitons à nous contacter pour confirmer ou valider la simulation.
|
||||
</div>
|
||||
<a href="/support" class="btn btn-sm btn-primary">Contacter le support</a>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
// Ajout du debug chômage
|
||||
const dbg = getChomageDebugInfo();
|
||||
const dbgHtml = dbg ? `
|
||||
<div style="margin-top:12px;padding:8px;border:1px dashed #cbd5e1;border-radius:8px;background:#f8fafc">
|
||||
<div style="font-weight:600;margin-bottom:4px">Debug chômage</div>
|
||||
<div>Branche: <strong>${dbg.branch}</strong> — Jours sélectionnés: <strong>${dbg.nbJours}</strong>, Cachets: <strong>${dbg.cachets}</strong>, Jours retenus chômage: <strong>${dbg.nbJoursChomage}</strong></div>
|
||||
<div>Jours dans le mois: <strong>${dbg.daysInMonth}</strong>, DiffDays (span): <strong>${dbg.diffDays}</strong></div>
|
||||
<div>Assiette chômage max calculée: <strong>${fmtEuro(dbg.assiette)}</strong> €</div>
|
||||
</div>` : '';
|
||||
|
||||
const resultTable = `
|
||||
<table class="result-table">
|
||||
<tr>
|
||||
|
|
@ -1205,6 +1452,7 @@ document.getElementById('calcBtn').addEventListener('click', function() {
|
|||
</tr>
|
||||
</table>
|
||||
<p>Plafond URSSAF : ${fmtEuro(plafondUrssaf)} €</p>
|
||||
${dbgHtml}
|
||||
`;
|
||||
document.getElementById('result').innerHTML = resultTable;
|
||||
document.getElementById('detailTable').innerHTML = generateDetailTable(brut, cachets, heures, plafondUrssaf);
|
||||
|
|
|
|||
Loading…
Reference in a new issue