Oracle Document Generator Pre-Built function

Il s’agit d’un service prêt à l’emploi sous forme d’image Docker, disponible dans le cloud Oracle, qui permet de générer des documents au format PDF à partir de :

  • Une source de données au format JSON
  • Un modèle (template) au format MS Word ou MS Excel

Ce service s’appuie sur l’utilisation du service de stockage (Storage) car les templates et les documents générés doivent être enregistrés dans des buckets. Les données, elles, peuvent être soit envoyées à la volée, soit être enregistrées au format JSON dans un bucket également.

Dans mon test, j’ai pu vérifier que l’on pouvait produire des documents avec plusieurs niveaux de répétitivité.
L’avantage principal de ce service Document Generator est qu’on bénéficie de toute la puissance de MS Word pour la conception.

La tarification est plutôt cool si on reste en dessous d’un seuil de 400.000 appels/mois.
cf Price List (chercher le mot clef: function)

Echantillon

J’ai repris le modèle de facture (Facture de vente professionnelle) qui est fourni avec MS Word.

J’ai enlevé tous les content controls et je les ai remplacés par des libellés statiques et par des tags correspondant à la source de données.

Les structures répétitives font l’objet de tag particuliers: {#node} {/node}
cf documentation sur les tags pour MS Word
Dans cet exemple, il y a deux boucles imbriquées de contrôle (pour chaque facture, et pour chaque ligne de facture)


Appel de la fonction de génération de document

  • Soit par l’interface OCI CLI
  • Soit en utilisant un SDK (l’appli donnée en exemple avec APEX utilise le SDK PL/SQL)
  • Soit en utilisant un appel REST assorti d’un Web Credential défini dans APEX.
    C’est la méthode que j’ai utilisée dans le cas présent. Facile à mettre en place dans APEX car le passage des credentials (qui nécessite une signature de l’appel) est masqué par APEX.

cf Documentation sur l’appel des PBF

Résultat PDF

Components

Payload

Ce sont les informations envoyées à la fonction « Document Generator » lui indiquant la localisation du template et de la source de données. La requestType est SINGLE (un seul document en sortie) ou bien BATCH (autant de documents PDF qu’il y a d’items dans la source de données qui, dans ce cas, doit se présenter comme un array.

{
  "requestType": "SINGLE",
  "tagSyntax": "DOCGEN_1_0",
  "data": {
    "source": "OBJECT_STORAGE",
    "namespace": "xxxxxxxxxxxxx",
    "bucketName": "pdf",
    "objectName": "orders.json",
    "contentType": "application/json"
  },
  "template": {
    "source": "OBJECT_STORAGE",
    "namespace": "xxxxxxxxxxxxx",
    "bucketName": "pdf",
    "objectName": "orders.docx",
    "contentType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
  },
  "output": {
    "target": "OBJECT_STORAGE",
    "namespace": "xxxxxxxxxxxxx",
    "bucketName": "pdf",
    "objectName": "orders.pdf",
    "contentType": "application/pdf"
  }
}

MS Word template (orders.docx)

Data source orders.json

{
	"ORDERS": {
		"HEADER": {
			"report_date": "Friday 10 January 2025",
			"TITLE": "",
			"DD": "Friday 10",
			"D": "10",
			"DAY": "Friday",
			"MONTH": "January",
			"YEAR": "2025"
		},
		"ORDERS_REC": [
			{
				"ORDER_ID": "652",
				"ORDER_DATETIME": "2023-08-25T03:30:13.000000",
				"CUSTOMER_ID": "374",
				"ORDER_STATUS": "COMPLETE",
				"STORE_ID": "1",
				"CUSTOMERS_CUSTOMER_ID": "374",
				"CUSTOMERS_FULL_NAME": "Irene Moore",
				"CUSTOMERS_EMAIL_ADDRESS": "irene.moore@internalmail",
				"STORES_STORE_ID": "1",
				"STORES_STORE_NAME": "Online",
				"STORES_WEB_ADDRESS": "https://www.example.com",
				"ORDER_FOOTER_FOP_V": {
					"ORDER_FOOTER_FOP_V_ORDERS_REC": {
						"ORDER_ID": "652",
						"TOTAL": "143.24",
						"TVA": "28.65",
						"TTC": "171.89"
					}
				},
				"ORDER_ITEMS_FOP_V": {
					"ORDER_ITEMS_FOP_V_ORDERS_REC": [
						{
						"ORDER_ID": "652",
						"PRODUCT_ID": "13",
						"PRODUCT_NAME": "Boy's Hoodie (Grey)",
						"QUANTITY": "2",
						"UNIT_PRICE": "12.64",
						"MNT": "25.28"
						},
						{
						"ORDER_ID": "652",
						"PRODUCT_ID": "44",
						"PRODUCT_NAME": "Women's Coat (Black)",
						"QUANTITY": "3",
						"UNIT_PRICE": "39.32",
						"MNT": "117.96"
						}
					]
				}
			},
			{
				"ORDER_ID": "653",
				"ORDER_DATETIME": "2023-08-25T07:17:39.000000",
				"CUSTOMER_ID": "293",
				"ORDER_STATUS": "COMPLETE",
				"STORE_ID": "1",
				"CUSTOMERS_CUSTOMER_ID": "293",
				"CUSTOMERS_FULL_NAME": "Diana Fowler",
				"CUSTOMERS_EMAIL_ADDRESS": "diana.fowler@internalmail",
				"STORES_STORE_ID": "1",
				"STORES_STORE_NAME": "Online",
				"STORES_WEB_ADDRESS": "https://www.example.com",
				"ORDER_FOOTER_FOP_V": {
					"ORDER_FOOTER_FOP_V_ORDERS_REC": {
						"ORDER_ID": "653",
						"TOTAL": "230.48",
						"TVA": "46.1",
						"TTC": "276.58"
					}
				},
				"ORDER_ITEMS_FOP_V": {
					"ORDER_ITEMS_FOP_V_ORDERS_REC": [
						{
						"ORDER_ID": "653",
						"PRODUCT_ID": "28",
						"PRODUCT_NAME": "Men's Hoodie (Red)",
						"QUANTITY": "3",
						"UNIT_PRICE": "10.24",
						"MNT": "30.72"
						},
						{
						"ORDER_ID": "653",
						"PRODUCT_ID": "36",
						"PRODUCT_NAME": "Women's Trousers (Blue)",
						"QUANTITY": "3",
						"UNIT_PRICE": "49.12",
						"MNT": "147.36"
						},
						{
						"ORDER_ID": "653",
						"PRODUCT_ID": "12",
						"PRODUCT_NAME": "Boy's Socks (White)",
						"QUANTITY": "5",
						"UNIT_PRICE": "10.48",
						"MNT": "52.4"
						}
					]
				}
			}
			
		]
	}
}

Remarques & limitations

Je n’ai pas réussi à déclencher un saut de page avec {?pagebreak} tag.

Si les noms de tags sont volumineux, cela diminue la lisibilité du template. Il aurait fallu pouvoir cacher les informatiques techniques, un peu comme dans un Content Control de Word.

Je n’ai pas vu de tag conditionnant un paragraphe (bien que le tag filter y ressemble un peu).

Le premier appel à la fonction (cold start) est très long (une minute). Les suivants sont rapides (2 sec).
La documentation donne des explications là-dessus (provisioned concurrency). Il est ainsi possible de maintenir un bon niveau de réactivité en paramétrant la provisioned concurrency.
Garder cependant en tête que le fait de maintenir des fonctions « prête à bondir », mais non sollicitées, est facturé en sus. (cf pricing des fonctions)

Je pense que ce service est destiné à la production de documents unitaires (un appel, un document). J’ai parfois constaté, sinon, des pb de timeout. D’après la doc, il semble qu’une fonction ne puisse produire plus de 6 Mo en output. Cela expliquerait le timeout car il y avait une centaine de factures à générer et chacune prend environ 60 Ko dans le pdf.

Dans le payload, il ne semble pas possible d’indiquer une URL pour accéder aux données. C’est soit INLINE, soit OBJECT_STORAGE.

Liens utiles

https://blogs.oracle.com/apex/post/seamless-pdf-generation-with-oracle-apex-and-oci-document-generator

https://www.linkedin.com/pulse/pdf-generation-oci-pre-built-function-kris-rice-gbree

https://tm-apex.hashnode.dev/start-generating-dynamic-pdfs-in-apex-easily-with-oci-pre-built-functions

About the author

GPM Factory