Version de développement
Cette documentation décrit la version « next » (develop), non encore publiée. Pour la version stable, basculez sur « v5 » dans l'en-tête.
Interface utilisateur

Constructeur de format d'impression - Personnalisation avancée

Constructeur de format d'impression - Personnalisation avancée

Pour les développeurs et administrateurs système Cette documentation couvre les aspects techniques et les personnalisations avancées du constructeur de format d'impression. Pour une utilisation standard, consultez le guide utilisateur.

Architecture technique

Le constructeur de format d'impression est une application Vue.js intégrée à Dodock, utilisant :

  • Store Pinia : Gestion centralisée de l'état (sélection, presse-papiers, historique)
  • SortableJS : Gestion du glisser-déposer
  • Mermaid : Génération de diagrammes dans la documentation
  • localStorage : Persistance du presse-papiers

Structure des fichiers

frappe/public/js/print_format_builder/
├── components/
│   ├── editor/
│   │   ├── Field.vue
│   │   ├── PrintFormat.vue
│   │   ├── PrintFormatSection.vue
│   │   └── ...
│   ├── inspector/
│   │   ├── BulkPropertiesPanel.vue
│   │   ├── FieldInspector.vue
│   │   └── ...
│   └── PrintFormatControls.vue
├── stores/
│   └── index.js
├── utils.js
└── PrintFormatBuilder.vue

Personnalisations avancées

Ajout de nouveaux types de blocs

Pour ajouter un nouveau type de bloc (ex: signature électronique) :

  1. Créez un composant Vue dans components/editor/
  2. Déclarez le bloc dans PrintFormatBuilder.vue
  3. Ajoutez les propriétés dans l'inspecteur

Exemple : Bloc Signature

// components/editor/SignatureField.vue
<template>
  <div class="signature-field">
    <img :src="signatureUrl" v-if="signatureUrl" />
    <div class="placeholder" v-else>Signature</div>
  </div>
</template>

<script>
export default {
  props: {
    field: Object
  },
  computed: {
    signatureUrl() {
      return this.field.options?.signature_field
        ? frappe.get_doc(this.field.doc_type, this.field.doc_name)
            .then(doc => doc[this.field.options.signature_field])
        : null;
    }
  }
}
</script>

Modification des règles de visibilité conditionnelle

Les règles de visibilité utilisent une évaluation sécurisée côté serveur. Pour étendre les variables disponibles :

# frappe/utils/print_format_generator.py
def evaluate_condition(condition, doc, row=None):
    # Variables disponibles : doc, row, print_settings
    safe_globals = {
        'doc': doc.as_dict(),
        'row': row.as_dict() if row else {},
        'print_settings': frappe.get_doc("Print Settings").as_dict(),
        # Ajoutez vos variables personnalisées ici
        'custom_utils': {
            'is_vip': lambda: doc.custom_vip_customer == 1
        }
    }
    try:
        return frappe.safe_eval(condition, safe_globals)
    except:
        return True  # Fail-safe

API JavaScript

Store Pinia

// Accès au store depuis un composant
import { usePrintFormatStore } from "../stores";

const store = usePrintFormatStore();

// Méthodes disponibles
store.addSection();
store.duplicateSelected();
store.applyPreset(presetName);
store.saveSnippet(snippetName);

Événements

ÉvénementDescriptionPayload
field-selectedChamp sélectionné{ field }
section-addedSection ajoutée{ section }
format-savedFormat enregistré{ format }
clipboard-updatedPresse-papiers modifié`{ action: 'copy'

Dépannage technique

Problèmes courants et solutions

ProblèmeCause probableSolution
Constructeur ne charge pasConflit de versionsVérifiez la version de Vue.js et Pinia
Glisser-déposer ne fonctionne pasConflit avec d'autres bibliothèquesDésactivez les extensions navigateur
Styles non appliquésCache navigateurVide le cache ou passe en mode navigation privée
Presse-papiers non persistantlocalStorage désactivéVérifiez les paramètres de confidentialité
Règles de visibilité ignoréesErreur de syntaxeUtilisez frappe.safe_eval pour déboguer

Journalisation

Activez les logs détaillés avec :

// Dans frappe/public/js/print_format_builder/utils.js
frappe.provide("pfb.utils");
pfb.utils.debug = true;

Les logs apparaissent dans la console navigateur avec le préfixe [PFB].

Tests et validation

Tests unitaires

// Exemple de test pour les règles de visibilité
describe("Condition evaluation", () => {
  it("should hide empty columns", () => {
    const doc = { items: [{ qty: 0, amount: 0 }] };
    const result = evaluate_condition("row.qty > 0", doc, doc.items[0]);
    expect(result).toBe(false);
  });
});

Tests d'intégration

  1. Parité visuelle : Comparez le rendu PDF avec le canevas
  2. Accessibilité : Vérifiez les rôles ARIA et la navigation clavier
  3. Performance : Mesurez le temps de rendu pour les documents complexes

Migration des formats personnalisés

Script de migration

# hooks.py
def after_migrate():
    migrate_custom_print_formats()

def migrate_custom_print_formats():
    for pf in frappe.get_all("Print Format", filters={"custom_format": 1}):
        doc = frappe.get_doc("Print Format", pf.name)
        if doc.format_data and "<div" in doc.format_data:
            # Conversion des formats HTML personnalisés
            doc.format_data = convert_html_to_builder(doc.format_data)
            doc.save()

Conversion des macros HTML

Les macros HTML doivent être converties en sections du constructeur :

<!-- Avant -->
<div class="custom-header">
  {{ doc.company_name }}
</div>

<!-- Après -->
<section>
  <field fieldname="company_name" />
</section>

Extensions recommandées

Module de prévisualisation avancée

// Ajoutez ce composant pour une prévisualisation en temps réel
frappe.ui.form.on("Print Format", {
  refresh(frm) {
    frm.add_custom_button(__("Prévisualisation avancée"), () => {
      open_preview_modal(frm.doc);
    });
  }
});

function open_preview_modal(doc) {
  const dialog = new frappe.ui.Dialog({
    title: "Prévisualisation",
    size: "extra-large",
    fields: [
      {
        fieldtype: "HTML",
        fieldname: "preview"
      }
    ]
  });
  
  frappe.call({
    method: "frappe.utils.print_format.generate_html",
    args: { doc: doc.name },
    callback: (r) => {
      dialog.fields_dict.preview.$wrapper.html(r.message);
    }
  });
  
  dialog.show();
}

Intégration avec les modèles de document

# print_format.py
def get_print_format_for_doctype(doctype):
    """Retourne le format d'impression par défaut pour un doctype"""
    defaults = frappe.get_all("Default Print Format",
        filters={"parent": doctype},
        fields=["print_format"])
    
    if defaults:
        return defaults[0].print_format
    
    # Retourne le premier format standard trouvé
    return frappe.get_all("Print Format",
        filters={"doc_type": doctype, "standard": "Yes"},
        limit_page_length=1)[0].name if frappe.get_all("Print Format",
        filters={"doc_type": doctype, "standard": "Yes"}) else None

Sécurité

Bonnes pratiques

  • Échappement des données : Utilisez toujours frappe.utils.escape_html
  • Validation des règles : Limitez les variables disponibles dans safe_eval
  • Permissions : Vérifiez les droits d'accès avant toute modification
  • localStorage : Chiffrez les données sensibles du presse-papiers

Audit de sécurité

# Vérification des règles de visibilité dangereuses
def audit_print_format_conditions():
    dangerous_patterns = [
        "__",
        "frappe.",
        "os.",
        "import ",
        "exec("
    ]
    
    for pf in frappe.get_all("Print Format"):
        doc = frappe.get_doc("Print Format", pf.name)
        if doc.format_data:
            for pattern in dangerous_patterns:
                if pattern in doc.format_data:
                    frappe.log_error(
                        title="Dangerous print format condition",
                        message=f"Format {pf.name} contains {pattern}"
                    )

Performances

Optimisations

  1. Chargement paresseux : Chargez les composants uniquement lorsque nécessaire
  2. Mémoïsation : Cachez les résultats des calculs coûteux
  3. Debouncing : Limitez les mises à jour pendant le glisser-déposer
  4. Virtualisation : Pour les documents avec >100 éléments

Exemple de debouncing :

// Dans PrintFormatBuilder.vue
import { debounce } from "lodash";

methods: {
  handleDrag: debounce(function() {
    this.updateLayout();
  }, 100)
}

Benchmarks

ActionTemps moyen (ms)Mémoire (MB)
Chargement initial45012
Ajout de section802
Glisser-déposer1203
Génération PDF180045

Roadmap

Fonctionnalités prévues

  • Thèmes prédéfinis : Sélection de thèmes professionnels
  • Export/Import : Partage de formats entre instances
  • Versioning : Historique des modifications
  • Collaboration : Édition simultanée
  • Intégration CI/CD : Tests automatisés des formats

Contribution

  1. Forkez le dépôt Dokos
  2. Créez une branche feat/print-format-enhancement
  3. Soumettez une MR avec tests et documentation

Références

Cette documentation est destinée aux développeurs et administrateurs système. Pour une utilisation standard, consultez le guide utilisateur.