{% extends 'base.html.twig' %} {% block title %}{{ commercial.user.fullName }} - Commercial - Admin{% endblock %} {% block body %}
{# ================================================================ HEADER ================================================================ #}
{% include '_molecules/page-header/page-header.html.twig' with { title: commercial.user.fullName, subtitle: commercial.user.email, icon: 'wallet', backHref: path('admin_commercial_list'), backLabel: 'Retour aux commerciaux' } only %} {# Même carte que le dashboard commercial : le code est lui-même la zone cliquable. #}
Code de parrainage
{% include 'admin/commercial/_copy-script.html.twig' %} {# ================================================================ STATISTIQUES ================================================================ #}
{# Taux de commission : éditable en place (comme les tokens des utilisateurs) #} {% include '_molecules/stat-card/stat-card.html.twig' with { label: 'Taux de commission', value: (commercial.commissionRate * 100)|number_format(2, ',', ' ') ~ '%', icon: 'chart', variant: commercial_colors.commission, size: 'sm', layout: 'horizontal', editModalId: 'modal-edit-rate' } only %} {% include '_molecules/stat-card/stat-card.html.twig' with { label: 'Filleuls', value: recruitPagination.totalItems, icon: 'users', variant: commercial_colors.filleuls, size: 'sm', layout: 'horizontal' } only %} {% include '_molecules/stat-card/stat-card.html.twig' with { label: 'Statut', value: commercial.isActive ? 'Actif' : 'Suspendu', icon: 'power', variant: commercial.isActive ? 'success' : 'error', size: 'sm', layout: 'horizontal' } only %} {# 4ᵉ case : activer / désactiver le commercial #}
Accès du commercial
{# ================================================================ MODALE — Édition du taux de commission (ouverte via le stat-card) ================================================================ #} {% embed '_molecules/modal/modal.html.twig' with { id: 'modal-edit-rate', title: 'Taux de commission', variant: 'primary', size: 'md' } %} {% block body %}

En pourcentage (ex : 15 pour 15 %). Automatiquement borné entre 0 et 100 %.

%
{% endblock %} {% block footer %} {% include '_atoms/button/button.html.twig' with { label: 'Annuler', variant: 'ghost', size: 'md', attr: { onclick: "closeModal('modal-edit-rate')" } } only %} {% include '_atoms/button/button.html.twig' with { label: 'Enregistrer', variant: 'primary', size: 'md', attr: { onclick: "document.getElementById('edit-rate-form').submit()" } } only %} {% endblock %} {% endembed %} {# Modal de confirmation, déclenché par le bouton de la status-zone ci-dessus #} {% embed '_molecules/modal/modal.html.twig' with { id: 'modal-toggle-commercial-status', title: commercial.isActive ? 'Suspendre ce commercial ?' : 'Réactiver ce commercial ?', variant: commercial.isActive ? 'danger' : 'success', size: 'md' } %} {% block body %}

{{ commercial.isActive ? 'Il perdra immédiatement l\'accès à son espace commercial (dashboard, filleuls, gains).' : 'Il retrouvera immédiatement l\'accès à son espace commercial.' }}

{% endblock %} {% block footer %}
{% include '_atoms/button/button.html.twig' with { label: 'Annuler', variant: 'ghost', size: 'lg', type: 'button', attr: { onclick: "closeModal('modal-toggle-commercial-status')" } } only %} {% include '_atoms/button/button.html.twig' with { label: commercial.isActive ? 'Confirmer la suspension' : 'Confirmer la réactivation', variant: commercial.isActive ? 'error' : 'success', size: 'lg', type: 'submit' } only %}
{% endblock %} {% endembed %} {# ================================================================ FILLEULS (projection RGPD via RecruitViewModel) ================================================================ #}

Filleuls ({{ recruitPagination.totalItems }})

{% if recruits|length > 0 %}
{% for recruit in recruits %} {% endfor %}
Nom Email Plan Statut Recruté le
{{ recruit.fullName }} {{ recruit.email }} {% include '_atoms/plan-badge/plan-badge.html.twig' with { name: recruit.planName, gradient: recruit.planGradient, isFree: recruit.planIsFree, isAnnual: recruit.planIsAnnual } only %} {% include '_atoms/badge/badge.html.twig' with { label: recruit.status, variant: recruit.status == 'active' ? 'success' : 'neutral', size: 'sm' } only %} {{ recruit.recruitedAt ?? '—' }}
{% if recruitPagination.total > 1 %}
{% include '_molecules/pagination/pagination.html.twig' with { currentPage: recruitPagination.current, totalPages: recruitPagination.total, baseUrl: path('admin_commercial_show', {id: commercial.id}), pageParam: 'filleuls', size: 'sm' } only %}
{% endif %} {% else %}
{% include '_molecules/empty-state/empty-state.html.twig' with { title: 'Aucun filleul', description: 'Ce commercial n\'a encore recruté aucun filleul.', icon: 'users', variant: 'minimal' } only %}
{% endif %}
{# ================================================================ COMMISSIONS RÉCENTES (lignes brutes, avant regroupement en relevé) ================================================================ #}

Commissions récentes ({{ commissionPagination.totalItems }})

{% if commissions|length > 0 %}
{% for commission in commissions %} {% endfor %}
Date Plan CA net HT Taux appliqué Commission Relevé
{{ commission.paidAt|date('d/m/Y') }} {% set p = plansBySlug[commission.planSlug] ?? null %} {% include '_atoms/plan-badge/plan-badge.html.twig' with { name: p ? p.name : commission.planSlug, gradient: p ? p.gradientCss : null, isFree: p ? p.isFree() : (commission.planSlug is null), isAnnual: commission.billingCycle == 'annual' } only %} {{ commission.amountNetHt|number_format(2, ',', ' ') }} € {{ (commission.appliedRate * 100)|number_format(2, ',', ' ') }}% {{ commission.commissionAmount|number_format(2, ',', ' ') }} € {% if commission.isReversal %}(contre-passation){% endif %} {{ commission.payout ? commission.payout.period : '—' }}
{% if commissionPagination.total > 1 %}
{% include '_molecules/pagination/pagination.html.twig' with { currentPage: commissionPagination.current, totalPages: commissionPagination.total, baseUrl: path('admin_commercial_show', {id: commercial.id}), pageParam: 'lignes', size: 'sm' } only %}
{% endif %} {% else %}
{% include '_molecules/empty-state/empty-state.html.twig' with { title: 'Aucune commission', description: 'Aucune commission n\'a encore été générée pour ce commercial.', icon: 'wallet', variant: 'minimal' } only %}
{% endif %}
{# ================================================================ ENCOURS — CE QU'ON LUI DOIT, ET DE QUOI LE RÉGLER La fiche ne listait que les relevés DÉJÀ versés et ne portait aucun bouton « Payer » : le seul écran dédié à un commercial ne disait pas ce qu'on lui doit. Une ligne par mois dû, chacune réglable séparément — la liste, elle, ne propose que le mois le plus ancien. ================================================================ #}

Encours dû {% if not due.isEmpty() %} ({{ due.count() }} mois) {% elseif due.hasCurrent() %} (mois en cours seulement) {% endif %}

Total à virer {{ due.total|number_format(2, ',', ' ') }} € {% if due.hasCurrent() %} + {{ due.currentTotal()|number_format(2, ',', ' ') }} € en cours {% endif %}
{# Le tableau s'ouvre aussi quand SEUL le mois en cours porte quelque chose : l'administrateur n'a plus à attendre le 1er du mois suivant pour voir venir un virement. La ligne du mois ouvert n'a ni bouton ni champ de référence — un mois n'est versable qu'une fois clos. #} {% if not due.isEmpty() or due.hasCurrent() %} {# Le tableau défile dans son propre conteneur : la colonne d'action porte un champ de référence et un bouton, elle ne se comprime pas. #}
{% for period in due.periods %} {% endfor %} {% if due.hasCurrent() %} {% set enCours = due.current %} {% endif %}
Mois Commission acquise Report entrant Montant à virer Action
{{ period.label() }} {{ period.commissionAmount|number_format(2, ',', ' ') }} € {{ period.carryOverIn|number_format(2, ',', ' ') }} € {{ period.amountPayable|number_format(2, ',', ' ') }} €
{% include '_atoms/button/button.html.twig' with { label: 'Payer', variant: 'success', size: 'sm', icon: 'check', type: 'submit' } only %}
{{ enCours.label() }} en cours — se clôt le {{ 'last day of this month'|date('d/m/Y') }} {{ enCours.commissionAmount|number_format(2, ',', ' ') }} € {{ enCours.carryOverIn|number_format(2, ',', ' ') }} € {{ enCours.amountPayable|number_format(2, ',', ' ') }} € Provisoire — pas encore versable
{% if not due.isEmpty() %} {% include '_molecules/inline-alert/inline-alert.html.twig' with { variant: 'warning', message: 'Le versement est manuel : enregistrez-le ici après avoir émis le virement, en reportant sa référence bancaire. Un mois versé est définitif — il n\'est jamais recalculé, et rien ne permet aujourd\'hui de l\'annuler.' } only %} {% else %} {% include '_molecules/inline-alert/inline-alert.html.twig' with { variant: 'primary', message: 'Aucun mois clos ne reste dû. Le montant ci-dessus est celui du mois en cours : il continue de bouger et ne sera versable qu\'une fois le mois terminé.' } only %} {% endif %}
{% else %}
{% include '_molecules/empty-state/empty-state.html.twig' with { title: 'Rien à virer', description: 'Aucun mois clos ne reste dû à ce commercial. Un mois n\'est versable qu\'une fois terminé.', icon: 'check-circle', variant: 'minimal' } only %}
{% endif %}
{# ================================================================ RELEVÉS DE COMMISSION ================================================================ #}

Relevés de commission ({{ payoutPagination.totalItems }})

{% if payouts|length > 0 %}
{# La référence bancaire OUVRE le tableau : c'est par elle qu'un relevé se rapproche du virement réel, sur le relevé de banque comme dans un échange avec le commercial. #} {% for payout in payouts %} {% endfor %}
Référence Période CA net HT Frais Stripe Commission Statut
{% if payout.reference %} {{ payout.reference }} {% else %} {# Les relevés d'avant l'obligation, et ceux restés « pending » que l'ancienne commande de génération créait sans virement. #} {% endif %} {{ payout.period|mois_fr }} {{ payout.revenueHt|number_format(2, ',', ' ') }} € {{ payout.stripeFees|number_format(2, ',', ' ') }} € {{ payout.commissionAmount|number_format(2, ',', ' ') }} € {% include '_atoms/badge/badge.html.twig' with { label: payout.isPaid ? 'Versé' : 'Jamais versé', variant: payout.isPaid ? 'success' : 'warning', size: 'sm' } only %}
{% if payoutPagination.total > 1 %}
{% include '_molecules/pagination/pagination.html.twig' with { currentPage: payoutPagination.current, totalPages: payoutPagination.total, baseUrl: path('admin_commercial_show', {id: commercial.id}), pageParam: 'releves', size: 'sm' } only %}
{% endif %} {% else %}
{% include '_molecules/empty-state/empty-state.html.twig' with { title: 'Aucun relevé', description: 'Aucun relevé de commission n\'a encore été généré pour ce commercial.', icon: 'chart', variant: 'minimal' } only %}
{% endif %}
{# ================================================================ HISTORIQUE ================================================================ #} {# La section s'arrête aux dernières lignes : une commission tombe à chaque paiement d'un filleul, et l'aperçu ne couvre alors que quelques semaines. #} {% include '_organisms/activity-log/activity-log-section.html.twig' with { logs: logs ?? [], moreHref: path('admin_commercial_journal', {id: commercial.id}) } only %}
{% endblock %}