{% import 'report/_chart_macros.html.twig' as C %} {% set include = (payload.include ?? {}) %} {% set currency = project.currency ?? 'XOF' %} {% set totalAllocatedBudget = project.totalBudget %} {% set ms = milestones|default([]) %} {% set avgProg = project.progress %}
KPLAN • {{ organization|default('Organisation') }}
{% if include.meta %} {% if project.program|default(null) %} Programme : {{ project.program.name|default(project.program) }} • {% endif %} Projet {{ project.name|default(project.acronym|default('—')) }} {% endif %}
Généré le {{ "now"|date("d/m/Y") }}

{{ report.title }}

Période : {{ report.periodStart ? report.periodStart|date('d/m/Y') : '—' }} → {{ report.periodEnd ? report.periodEnd|date('d/m/Y') : '—' }}
{{ C.donut(avgProg|default(0), 60, 15, '#eef2ff', '#007f3f', 'Avancement') }}
Progression globale
{{ avgProg|default(0)|round(1) }}%
Budget total
{{ (project.totalBudget|default(project.totalBudget ))|format_currency(currency,{fraction_digit:0}) }}
Engagé: {{ (project.totalCommittedBudget)|format_currency(currency,{fraction_digit:0}) }}
Décaissé: {{ (project.totalDisbursedBudget)|format_currency(currency,{fraction_digit:0}) }}
Suivi par
{% if project.monitor|default(null) and project.monitor.fullname|default(null) %} {{ project.monitor.fullname }} {% else %} - {% endif %}
Structure
{{ project.department.abbreviation|default('—') }}
Chef de projet
{% if project.responsible|default(null) and project.responsible.fullname|default(null) %} {{ project.responsible.fullname }} {% else %} {{ project.status|default('—') }} {% endif %}
Jours restants
{% set today = "now"|date("Y-m-d") %} {% set endDate = project.endDate|default(null) %} {% if endDate and endDate > today %} {{ (endDate|date('U') - today|date('U')) / 86400 | round(0) }}j {% else %} Terminé {% endif %}
Tâches complétées
78%
{% set committedPct = (project.totalCommittedBudget / project.totalBudget *100) %}
Budget engagé
{{ committedPct | number_format(0) }}%
{% set disbursedPct = (project.totalDisbursedBudget / project.totalBudget *100) %}
Budget liquidé
{{ disbursedPct | number_format(0) }}%
{% set today = "now"|date("Y-m-d") %} {% set endDate = project.endDate|default(null) %} {% if endDate and project.startDate %} {% set totalDays = (endDate|date('U') - project.startDate|date('U')) / 86400 %} {% set elapsedDays = (today|date('U') - project.startDate|date('U')) / 86400 %} {% set elapsedPct = (elapsedDays / totalDays) * 100 %} {% else %} {% set elapsedPct = 0 %} {% endif %}
Délai écoulé
{{ elapsedPct | number_format(0) }}%

Problèmes Récents

{% set items = issues|default([]) %} {% if items is empty %}
Aucun problème signalé
{% else %} {% set PRIORITY_COLOR = {'low':'#0ea5e9','medium':'#f59e0b','high':'#dc2626','critical':'#7f1d1d'} %} {% set STATUS_COLOR = {'open':'#0ea5e9','in_progress':'#f59e0b','resolved':'#16a34a','closed':'#6b7280'} %} {% for p in items|slice(0,3) %} {% set pr = p.priority|default('medium')|lower %} {% set st = p.status|default('open')|lower %}
{{ p.title|default('—') }} • {{ p.identificationDate|default(null) ? p.identificationDate|date('d/m/Y') : '—' }}
{{ ('reported_problem.priority.' ~ pr)|trans }} {{ ('reported_problem.status.' ~ st)|trans }} {% if p.activity|default(null) %} • {{ p.activity.name|default('') }}{% endif %}
{# Plan d’action (résolution) #} {% if p.termsOfResolution|default(null) or p.solution|default(null) %}
Plan d’action
{% if p.termsOfResolution|default(null) %}
Voie de résolution : {{ p.termsOfResolution }}
{% endif %} {% if p.solution|default(null) %}
{% set lines = (p.solution|trim)|split('\n') %} {% if lines|length > 1 %}
    {% for line in lines %} {% set t = line|trim %} {% if t is not empty %}
  • {{ t }}
  • {% endif %} {% endfor %}
{% else %} {{ p.solution|nl2br }} {% endif %}
{% endif %}
{% endif %}
{% endfor %} {% if items|length > 3 %}
+{{ items|length - 3 }} autres problèmes
{% endif %} {% endif %}

Indicateurs de Performance

{% if include.kpi %} {% set k = kpi|default({}) %} {% set cm = k.currentMonth|default({}) %} {% set lm = k.lastMonth|default({}) %} {% set mb = k.monthBeforeLast|default({}) %}
Tendance sur 3 mois
{{ C.spark_area([ mb.actual|default(0), lm.actual|default(0), lm.actual|default(0), lm.actual|default(0), mb.actual|default(0), cm.actual|default(0), mb.actual|default(0), cm.actual|default(0), mb.actual|default(0), lm.actual|default(0), cm.actual|default(0), ], 250, 30) }}
{% set rows = [ { 'label': 'M-2', 'planned': mb.planned|default(0), 'actual': mb.actual|default(0), 'gap': mb.gap|default(0) }, { 'label': 'M-1', 'planned': lm.planned|default(0), 'actual': lm.actual|default(0), 'gap': lm.gap|default(0) }, { 'label': 'M', 'planned': cm.planned|default(0), 'actual': cm.actual|default(0), 'gap': cm.gap|default(0) } ] %} {% for r in rows %} {% endfor %}
Période Planifié Réalisé Écart
{{ r.label }} {{ r.planned|number_format(0, ',', ' ') }} {{ r.actual|number_format(0, ',', ' ') }} {{ r.gap >= 0 ? '+' : '' }}{{ r.gap|number_format(0, ',', ' ') }}
{% else %}
Section non incluse
{% endif %}

Composition du Projet

{% set comps = componentsWithActivities|default([]) %} {% for component in comps %}

{{ component.name|default('Composante ' ~ loop.index) }}

● {{ component.status|trans|default('En cours') }} • {{ component.activitiesCount|default(0) }} activité(s) • {{ component.progress|default(0)|round(0) }}% complété
{{ C.donut(component.progress|default(0), 40, 10, '#eef2ff', '#f4c300', '') }}
Budget (A)
{{ component.allocated|default(0)|format_currency(currency,{fraction_digit:0}) }}
Engagé (E)
{{ component.committed|default(0)|format_currency(currency,{fraction_digit:0}) }}
Décaissé (D)
{{ component.disbursed|default(0)|format_currency(currency,{fraction_digit:0}) }}
{{ C.double_donut( component.allocated|default(0), component.committed|default(0), component.disbursed|default(0), 40, 10, ) }}
{{ C.gantt_svg(component.activities|default([]), 794, 200, 14, 8) }}
En cours ({{ component.activities|filter(a => a.status == 'in_progress')|length }})
Terminé ({{ component.activities|filter(a => a.status == 'completed')|length }})
Retard ({{ component.activities|filter(a => a.status == 'delayed')|length }})
Non débuté ({{ component.activities|filter(a => a.status == 'planned')|length }})

Activités récentes

{% if component.activities|default([]) is not empty %} {% for activity in component.activities|slice(0, 3) %}
{{ activity.name|default('Activité') }}
{{ activity.progress|number_format(0)|default(0) }}% • {{ activity.status|default('planned')|trans }} • {{ task_remaining_time(activity.startDate, activity.endDate) }}
{% endfor %} {% else %}
Aucune activité
{% endif %}

{% else %}
Aucune composante définie
{% endfor %}
Rapport généré avec KPLAN • {{ organization|default('') }} • {{ "now"|date("d/m/Y H:i") }}