{# Bloc documentaire partagé par l’aperçu et le futur rendu : une projection de données, aucune entité Doctrine. Paramètres : block (catalogue), document (liste blanche). Remplace les rendus locaux par cas client. #} {% macro rows(items, parentId, level, config) %} {% for item in items|filter(item => item.parentId == parentId) %} {{ item.type == 'section' ? 'Section · ' : item.type == 'phase' ? 'Phase · ' : (item.type == 'step' ? 'Étape · ' : 'Tâche · ') }}{{ item.name }}{% if config.detailLevel == 'detailed' and item.description %}

{{ item.description|nl2br }}

{% endif %} {% if config.detailLevel == 'detailed' %}{{ item.quantity }}{% endif %} {% if config.showWorkload %}{{ item.days|number_format(2, ',', ' ') }} j{% endif %} {{ item.amountHt|number_format(2, ',', ' ') }} € {% if item.type == 'section' or (config.detailLevel == 'standard' and level < 1) or config.detailLevel == 'detailed' %} {{ _self.rows(items, item.id, level + (item.type == 'section' ? 0 : 1), config) }} {% endif %} {% endfor %} {% endmacro %} {% set config = document.composition %} {% set financial = document.financialSummary %}

{{ block.label }}

{% if block.type == 'identity' %}

Devis {{ document.identity.number }} · {{ document.identity.subject }}

{{ document.issuer.name ?: 'Émetteur à renseigner' }} · {{ document.issuer.address }} · {{ document.issuer.siret }}

{% if document.issuer.legalNotice %}

{{ document.issuer.legalNotice|nl2br }}

{% endif %}

Offre {{ document.identity.offerNumber }} · Chiffrage V{{ document.identity.sourceEstimateVersion }} · Validité : {{ document.identity.validityValue }} {{ document.identity.validityUnit == 'month' ? 'mois' : 'jours' }} à compter de l’émission.

{% elseif block.type == 'client' %}

{{ document.client.name ?: 'Client à renseigner' }} {{ document.client.establishment }}

{{ document.client.address }} {{ document.client.postalCity }}
SIRET : {{ document.client.siret }} · TVA : {{ document.client.vatNumber }}

{{ document.client.contact }} {{ document.client.email }}

{% elseif block.type == 'subject' %}

{{ document.identity.subject }}

{{ document.identity.description|nl2br }}

{% elseif block.type == 'services' %} {% if config.detailLevel == 'detailed' %}{% endif %}{% if config.showWorkload %}{% endif %}{{ _self.rows(document.services, null, 0, config) }}
PrestationQuantitéChargeMontant HT

Les montants des phases et étapes sont des sous-totaux, non des lignes supplémentaires.

{% if config.pilotagePresentation == 'separate' and financial.pilotagePrice %}

Pilotage projet {% if config.showWorkload %}· {{ financial.pilotageDays|number_format(2, ',', ' ') }} j{% endif %} · {{ financial.pilotagePrice|number_format(2, ',', ' ') }} € HT

{% endif %} {% elseif block.type == 'deliverables' %} {% for item in document.services %}{% if item.deliverables %}

{{ item.name }}

{{ item.deliverables|nl2br }}

{% endif %}{% endfor %} {% elseif block.type == 'planning' %}

{{ document.planning.startsAt ? document.planning.startsAt|date('d/m/Y') : 'Début à préciser' }} — {{ document.planning.endsAt ? document.planning.endsAt|date('d/m/Y') : 'Fin à préciser' }}

{% elseif block.type == 'profiles' %} {% if config.showProfiles %}{% if config.showWorkload %}{% endif %}{% for profile in document.profiles %}{% if config.showWorkload %}{% endif %}{% endfor %}
ProfilTJM HTCharge
{{ profile.label }}{{ profile.dailyRate|number_format(2, ',', ' ') }} €{{ profile.days|number_format(2, ',', ' ') }} j
{% else %}

Affichage des profils désactivé.

{% endif %} {% elseif block.type == 'expenses' %}

Les frais de mission font l’objet de factures spécifiques.

{% for extra in document.extras|filter(extra => extra.kind == 'expense') %} {% if config.showExpenseDetails %}

{{ extra.label }} · {{ extra.mode == 'reel' ? 'Au réel — montant du chiffrage à confirmer' : 'Forfait' }} · {{ extra.amountHt|number_format(2, ',', ' ') }} € HT

{% endif %} {% endfor %}

Les frais au réel sont des prévisions ajustées selon les justificatifs.

{% elseif block.type == 'discount' %}

{{ financial.discountLabel }} : −{{ financial.discount|number_format(2, ',', ' ') }} € HT

{% elseif block.type == 'summary' %} {% for extra in document.extras %}{% endfor %}
Prestations, pilotage compris{{ financial.priceBeforeDiscount|number_format(2, ',', ' ') }} €
{{ financial.discountLabel }}−{{ financial.discount|number_format(2, ',', ' ') }} €
Prestations après remise{{ financial.priceProposed|number_format(2, ',', ' ') }} €
{{ extra.kind == 'expense' ? 'Frais — facture spécifique' : 'Achat / prestation externe' }} · {{ extra.label }}{{ extra.amountHt|number_format(2, ',', ' ') }} €
{% elseif block.type == 'total' %}

Total HT : {{ financial.totalHt|number_format(2, ',', ' ') }} €

{% if financial.vatRate is not null %}

TVA {{ financial.vatRate|number_format(2, ',', ' ') }} % : {{ financial.vatAmount|number_format(2, ',', ' ') }} €

Total TTC : {{ financial.totalTtc|number_format(2, ',', ' ') }} €

{% endif %} {% elseif block.type == 'billing' %} {% for category, label in {'services': 'Prestations', 'expenses': 'Frais — factures spécifiques'} %} {% set rows = document.paymentSchedule|filter(row => row.category == category) %} {% if rows %}

{{ label }}

{% for row in rows %}{% endfor %}
ÉchéanceDate d’émission prévueMontant HT
{{ row.name }}{{ row.plannedInvoiceAt ? row.plannedInvoiceAt|date('d/m/Y') : 'À préciser' }}{{ row.amountHt is not null ? row.amountHt|number_format(2, ',', ' ') ~ ' €' : 'À préciser' }}
{% endif %} {% endfor %} {% if document.paymentSchedule is empty %}

Échéancier à compléter.

{% endif %} {% elseif block.type == 'payment' %}

{{ document.paymentTerms|nl2br }}

{% elseif block.type == 'references' %}

{{ document.contractReference }}

{% elseif block.type == 'acceptance' %}

{{ document.acceptance }}

Nom :
Fonction :
Date :
Signature / cachet :

{% elseif block.type == 'cgps' %}

Version : {{ document.cgps.version ?: 'À renseigner' }}

{{ document.cgps.text ? document.cgps.text|nl2br : 'CGPS à renseigner dans les réglages.' }}

{% elseif block.editable %}

{{ block.content|nl2br }}

{% endif %}