Create multiple files recursively with templater

I’m having the same issue reported here: Using `tp.file.create_new` on Multiple Files in a Row with the Same Template Results in Files Getting Same Content · Issue #1080 · SilentVoid13/Templater · GitHub but applying the recomendations there I can’t make it work.

I have notes in my recurrentes folder with every expenses that have a recurring due date.

I have a template called movimientos_periodos with this code. This template ask me for which period I want to add the new notes. Then looks for all the notes in recurrentes folder, where the due date equals the selected period. Then gets all the metadata properties for this notes. Then I ask it to create a new note with the template _include_movimientos_fijos for each note meating the criteria. This is my movimientos_periodos template that I call with alt+n with Templater.

Note: There might be some unused or strange code, that I’ve been adding with all my tests, Or that I add to make clear which note has been created and is missing the template (that is why I added +"_"+recurrent.basename after my desire filename.


async function createFileIfNotExists(create_in_path, template_file, create_in) {
  let file_name ="YYYYMMDD_hhmmssSSS")+"_"+recurrent.basename;
  let file_path = `${create_in_path}/${file_name}.md`;
  console.log("template_file:", template_file);
  // Using async/await syntax here instead of .then to ensure we don't proceed to the next iteration until both the existence check and file creation completes
  const exists = await tp.file.exists(file_path);
  if (!exists) {
	//console.log("Creating holidays for ", file_name);
	// Awaiting so we're only creating one file at a time
	await tp.file.create_new(template_file, file_name, false, create_in);
	let recurrente;
	let recurrent = {};
	//let randomID;
	let estado;
	let MovVencimiento;
	let opcion = "todos";
	// Pido el periodo
	let periodo = "";
	periodo = await tp.user.system_suggester_folder("Data/Periodos", "Elegir Periodo", tp, app);
	// Obtener todas las notas recurrentes
	const dv = app.plugins.plugins.dataview.api;
	const todasLasNotasRecurrentes = dv.pagePaths('"Data/Recurrentes"'); 

	  for (let i = 0; i < todasLasNotasRecurrentes.length; ++i) {
		let notaRecurrente = todasLasNotasRecurrentes[i];
		// Verificar si notaRecurrente está definida
		if (notaRecurrente) {
			// Obtener el tfile y el frontmatter de metadataCache de la nota recurrente
			let recurrente_tfile = tp.file.find_tfile(notaRecurrente);  
			let recurrente_cache = app.metadataCache.getFileCache(recurrente_tfile);
			// Crear una variable 'recurrent' y asignar las propiedades del frontmatter
			for (let prop in recurrente_cache.frontmatter) {
				recurrent[prop] = recurrente_cache.frontmatter[prop];
			// Asignar las propiedades adicionales de tfile
			recurrent.path = recurrente_tfile.path;
			recurrent.basename = recurrente_tfile.basename;
			//console.log ("recurrent.basename", recurrent.basename);
			// Calcular el vencimiento para esta nota recurrente
			let MovVencimiento = await tp.user.calcular_movimiento_vencimiento(recurrent.vencimiento_recurrencia, periodo.title, recurrent.vencimiento_dia, recurrent.vencimiento_mes, recurrent.vencimiento_anio, tp, opcion);
			// Verificar si el vencimiento está dentro del periodo especificado
			if (MovVencimiento !== null) {
				// Crear una nota de movimiento
				let randomID = await tp.user.random_id(tp);
				estado = "PENDIENTE";
				let title = randomID;
				filename = randomID+"__"+recurrent.basename;    
				// Llamo a la función para generar cada archivo
				let create_in_path = "_inbox/testing_recurrent/";
				let create_in = app.vault.getAbstractFileByPath(create_in_path);
				let template_file = tp.file.find_tfile("_include_movimientos_fijos");
				await createFileIfNotExists(create_in_path, template_file, create_in);
		} else {
			console.log("La nota recurrente es undefined. Revisar el código para asegurar que notaRecurrente se esté asignando correctamente.");


the template _include_movimientos_fijos only has "asdfasdfasdf" as text for now. (problem aside… that I have not search a solution yet, is that if I try to pass information to that template like asdfasdfasdfasdf - <%* tR += randomID %> I get Templater Error: Template parsing error, aborting. randomID is not defined). I know that in the middle of hundreds of tests I’ve made, this was working fine, but now its not.

The problem I have is that when I execute the template the new notes are created, but the template is not always assigned to all notes. I have 3 notes in recurrentes folder, and in the new ones created at testing_recurrent the template is not applied to all notes. Sometimes two of them get it, other times only one.

If I change the function await tp.file.create_new(template_file, file_name, false, create_in); for tp.file.create_new("any text here", file_name, false, create_in); it’s still not applying the text “any text here” to all the notes created.

I tried using await tp.file.move("_inbox/testing_recurrent/" + filename); it recursively assigns the template to the same file. I end up having only one file that was converted and renamed three times.

I have been changing my templates to be called from Quickadd, so I have them all organized there. How can I make a command in quickadd that executes all that logic and creates one one for each recurring expense applying the template _include_movimientos_fijos ?

The period could be a quickadd capture, giving as options the notes I have in my periods folder, those notes are names 2024-02, 2024-04. It should have the option to add a new one If the one I need is not created. (I have a templater template for periods) that I can call.

The original _include_movimientos_fijos should include this



title: <%* tR += randomID %>
filename: <%* tR += randomID %>
tags: <%* /*tR += tags*/ %>
id: <%* tR += randomID %>
revisado: true

Fecha: <%* tR += MovVencimiento %>
DataTipo: "[[Movimientos]]"

MovTipo: <%* tR += recurrent.MovTipo %>
CostoTipo: <%* tR += recurrent.CostoTipo %>
recurrente: <%* tR += "[["+ recurrent.path + "|" + recurrent.basename + "]]" %>
vencimiento_recurrencia: <%* tR += recurrent.vencimiento_recurrencia %>
MovListaProvisorio: Vencimientos
MovDetalle: <%* tR += recurrent.MovDetalle %>
MovEstado:  <%* tR += estado %>
MovPeriodo:  <%* tR += periodo.title %>
MovVencimiento: <%* tR += MovVencimiento %>
MovDestino: "<%* tR += recurrent.MovDestino %>"
movCategoria: "<%* tR += recurrent.movCategoria %>"

ComprobanteGuardar: <%* tR += recurrent.ComprobanteGuardar %>
ComprasCargar: <%* tR += recurrent.ComprasCargar %>

ImporteARS: <%* tR += recurrent.ImporteARS %>
ImporteUSD: <%* tR += recurrent.ImporteUSD %>
TipoAsiento: <%* tR += recurrent.TipoAsiento %>


.. ARS:
EgresoARS(D): 0
IngresoARS(H): 0
.. USD:
EgresoUSD(D): 0
IngresoUSD(H): 0
EgresoTotal(D): 0
IngresoTotal(H): 0


## Relaciones

## Detalle:

## DocumentosContables:

## Extras:

Ignore the strange properties names and symbols, I hide them with CSS for now. They are placeholders to be replaced for actual properties if I need them. I’m still working in my expense workflow, so I use it to add things that I’m realizing now that I may need.

The system uses my calcular_movimiento_vencimiento.js that has this to check if each note should be considered to be added to the period:

async function calcular_movimiento_vencimiento(vencimiento_recurrencia, periodo, vencimiento_dia, vencimiento_mes, vencimiento_anio, tp, opcion) {
  const [periodo_anio, periodo_mes] = periodo.split("-").map(Number);
  let vencimiento_dia_num = parseInt(vencimiento_dia);
  let vencimiento_mes_num = parseInt(vencimiento_mes);
  let vencimiento_anio_num = parseInt(vencimiento_anio);

  switch (vencimiento_recurrencia) {
      case "mensual":
        return periodo_anio + "-" + periodo_mes.toString().padStart(2, '0') + "-" + vencimiento_dia_num.toString().padStart(2, '0');

      case "anual":
        // Si el mes del periodo y de la recurrencia coinciden, directamente pongo esa información.  
        if (periodo_mes === vencimiento_mes_num) {
          return periodo_anio + "-" + vencimiento_mes_num.toString().padStart(2, '0') + "-" + vencimiento_dia_num.toString().padStart(2, '0');
        // Si el mes del periodo es diferente del mes de la recurrencia con un suggester pregunto si cancelo o lo agrego con nuevo vencimiento.
        }else {
          if (opcion === "solo_uno") {
            const respuesta = await tp.system.suggester(["Sí", "No"], ["Sí", "No"], true, `Vencimiento anual mes ${vencimiento_mes} no coincide con periodo ${periodo} ¿Desea agregarlo igual?`);
            if (respuesta === "No") {
              return null;
            } else {
              // Solicitar un nuevo MovVencimiento
              MovVencimiento = await tp.user.date_picker(tp);
              return MovVencimiento;
          } else {
            return null;
      case "decenal":
          // Calcular el año de inicio y fin de la década
          const vencimiento_10_anios = vencimiento_anio_num + 10;
          // Verificar si el año del periodo coincide con el vencimiento_anio o con vencimiento_anio + 10 o con vencimiento_anio + 20, etc.
          const esDecenal = (periodo_anio >= vencimiento_anio_num && (periodo_anio - vencimiento_anio_num) % 10 === 0) || 
                            (periodo_anio >= vencimiento_10_anios && (periodo_anio - vencimiento_10_anios) % 10 === 0);
          if (periodo_mes === vencimiento_mes_num && esDecenal) {
              return periodo_anio + "-" + vencimiento_mes_num.toString().padStart(2, '0') + "-" + vencimiento_dia_num.toString().padStart(2, '0');
          } else {
            if (opcion === "solo_uno") {
              const respuesta = await tp.system.suggester(["Sí", "No"], ["Sí", "No"], true, `Vencimiento decenal ${vencimiento_anio}-${vencimiento_mes} no coincide con periodo ${periodo} ¿Desea agregarlo igual?`);
              if (respuesta === "No") {
                  return null;
              } else {
                  // Solicitar un nuevo MovVencimiento
                  MovVencimiento = await tp.user.date_picker(tp);
                  return MovVencimiento;
            } else {
              return null;

      case "personalizado":
        // Convertir el día y el mes en números enteros
        const lista_meses = vencimiento_mes.split(",").map(mes => parseInt(mes.trim()));
        // Generar las fechas personalizadas para cada mes
        const lista_fechas = => {
            return periodo_anio + "-" + mes + "-" + vencimiento_dia_num;
        // Verificar si el mes del periodo coincide con alguno de los meses personalizados
        if (lista_meses.includes(periodo_mes)) {
            return periodo_anio + "-" + periodo_mes.toString().padStart(2, '0') + "-" + vencimiento_dia_num.toString().padStart(2, '0');
        } else {
          if (opcion === "solo_uno") {
            const respuesta = await tp.system.suggester(["Sí", "No"], ["Sí", "No"], true, `Vencimiento los meses ${lista_meses} y no coincide con el periodo ${periodo}. ¿Desea agregarlo igual?`);
            if (respuesta === "No") {
                return null;
            } else {
                // Solicitar un nuevo MovVencimiento
                MovVencimiento = await tp.user.date_picker(tp);
                return MovVencimiento;
          } else {
            return null;

        console.warn(`Recurrencia no reconocida. Revisar Base de datos de Vencimientos Recurrentes, que no haya un vencimiento recurrente con un vencimiento_recurrente erróneo.`);
        return null;

module.exports = calcular_movimiento_vencimiento;

It also uses a user function system_suggester_folder.js with this code, that creates a suggester with notes of a folder

async function system_suggester_folder(path, suggesterHelp, tp, app) {

    // Busco los archivos en la ruta especificada
    const alternatives = [];
    const files = await app.vault.getMarkdownFiles().filter(x => x.parent?.path === path);

    // Busco el title para las alternativas encontradas anteriormente => {
        const fileCache = app.metadataCache.getFileCache(file);
        // Si el archivo no tiene especificado un title en el frontmatter, muestro el basename
        if (fileCache?.frontmatter?.title) {
            file.title = fileCache?.frontmatter?.title;
            file.title = file.basename;
        // Envío en las alternativas el path, basename y title que luego puedo usar en mi template
        alternatives.push({ path: file.path, basename: file.basename, title: file.title });
    // Armo el systemSuggester con las opciones obtenidas
    const selectedData = await tp.system.suggester(item => item.basename + (item?.title != item.basename ? `\n↳ ${ item?.title }` : ""), alternatives, true, suggesterHelp, 10);
    return selectedData;

module.exports = system_suggester_folder;

the movVencimiento that I apply to Fecha, is a date with the format 2024-04-18. I want to create the randomID adapting this movVencimiento to YYYYMMDD_HHmmssSSS, having the part HHmmssSSS to be the one from the moment this is being created.

something like

    const formattedDate = fecha.replace(/-/g, ''); // Eliminar los guiones
    id = formattedDate +"HHmmssSSS");

This would be the filename and the randomID that later I retrieve to apply to some child related notes.

Can you help me with the logic with Quickadd? I think I prefer this than the method only with templater, cause there it creates first an untitled note, that is still there after creating the other notes.

I hope I explained myself. (sorry for the typing mistakes, too much text and english is not my native language)

Anyone who can guide me in the right direction? I really need to make this work before may 1st, cause this is to make my recurrent expenses work.

Please, I really, really need to make this work. I keep trying things and nothing works. I think it may be possible with quickadd, but can’t figure it out. :slightly_frowning_face: