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.
Le constructeur de format d'impression est une application Vue.js intégrée à Dodock, utilisant :
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
Pour ajouter un nouveau type de bloc (ex: signature électronique) :
components/editor/PrintFormatBuilder.vueExemple : 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>
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
// 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énement | Description | Payload |
|---|---|---|
field-selected | Champ sélectionné | { field } |
section-added | Section ajoutée | { section } |
format-saved | Format enregistré | { format } |
clipboard-updated | Presse-papiers modifié | `{ action: 'copy' |
| Problème | Cause probable | Solution |
|---|---|---|
| Constructeur ne charge pas | Conflit de versions | Vérifiez la version de Vue.js et Pinia |
| Glisser-déposer ne fonctionne pas | Conflit avec d'autres bibliothèques | Désactivez les extensions navigateur |
| Styles non appliqués | Cache navigateur | Vide le cache ou passe en mode navigation privée |
| Presse-papiers non persistant | localStorage désactivé | Vérifiez les paramètres de confidentialité |
| Règles de visibilité ignorées | Erreur de syntaxe | Utilisez frappe.safe_eval pour déboguer |
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].
// 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);
});
});
# 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()
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>
// 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();
}
# 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
frappe.utils.escape_htmlsafe_eval# 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}"
)
Exemple de debouncing :
// Dans PrintFormatBuilder.vue
import { debounce } from "lodash";
methods: {
handleDrag: debounce(function() {
this.updateLayout();
}, 100)
}
| Action | Temps moyen (ms) | Mémoire (MB) |
|---|---|---|
| Chargement initial | 450 | 12 |
| Ajout de section | 80 | 2 |
| Glisser-déposer | 120 | 3 |
| Génération PDF | 1800 | 45 |
feat/print-format-enhancementCette documentation est destinée aux développeurs et administrateurs système. Pour une utilisation standard, consultez le guide utilisateur.