Schedules with google

For requests or help with our API
Post Reply
User avatar
jordansparks
Site Admin
Posts: 5770
Joined: Sun Jun 17, 2007 3:59 pm
Location: Salem, Oregon
Contact:

Schedules with google

Post by jordansparks » Fri Mar 25, 2022 7:32 am

I got the following as a private msg. Posting it here for everyone:

Here is the n8n / Javascript to perform a google calendar update w/o webhooks. I would like to reuse this code for schedules which requires the GET schedules/xxx for deletion. Google calendar will then update calendly. Code below.

Joerg


1st snipped: retrieve Apts subroutine used by Google Calendar update.

Code: Select all

{
  "nodes": [
    {
      "parameters": {},
      "name": "Start",
      "type": "n8n-nodes-base.start",
      "typeVersion": 1,
      "position": [
        240,
        300
      ]
    },
    {
      "parameters": {
        "filePath": "/home/node/.n8n/od_sync/timestamp.bin"
      },
      "name": "Read Apt DateTStamp last access",
      "type": "n8n-nodes-base.readBinaryFile",
      "typeVersion": 1,
      "position": [
        440,
        300
      ]
    },
    {
      "parameters": {
        "options": {}
      },
      "name": "Convert to JSON",
      "type": "n8n-nodes-base.moveBinaryData",
      "typeVersion": 1,
      "position": [
        640,
        300
      ]
    },
    {
      "parameters": {
        "mode": "mergeByIndex"
      },
      "name": "Merge",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 1,
      "position": [
        860,
        320
      ]
    },
    {
      "parameters": {
        "value": "={{ new Date(new Date().getTime()) }}",
        "dataPropertyName": "DateToday",
        "toFormat": "YYYY-MM-DD",
        "options": {}
      },
      "name": "Today",
      "type": "n8n-nodes-base.dateTime",
      "typeVersion": 1,
      "position": [
        640,
        500
      ]
    },
    {
      "parameters": {
        "mode": "jsonToBinary",
        "options": {
          "mimeType": "application/json"
        }
      },
      "name": "Move Binary Data",
      "type": "n8n-nodes-base.moveBinaryData",
      "typeVersion": 1,
      "position": [
        2420,
        300
      ]
    },
    {
      "parameters": {
        "operation": "sort",
        "sortFieldsUi": {
          "sortField": [
            {
              "fieldName": "DateTStamp",
              "order": "descending"
            }
          ]
        },
        "options": {}
      },
      "name": "Sort by DateTStamp",
      "type": "n8n-nodes-base.itemLists",
      "typeVersion": 1,
      "position": [
        1980,
        300
      ],
      "continueOnFail": true
    },
    {
      "parameters": {
        "functionCode": "// only retrieve future appointments with updated last acces DateTStamp\n// this is to avoid a full update after open dental software upgrade that involves touching all mysql tables\nlet baseurl = 'https://api.opendental.com/api/v1/appointments?Limit=50&dateStart='+$json[\"DateToday\"]+'&DateTStamp='+$json[\"DateTStamp\"]\n\n//Alternate URL for testing\n//let baseurl = 'https://api.opendental.com/api/v1/appointments?Limit=50&DateTStamp='+$json[\"DateTStamp\"]\n//let baseurl = 'https://api.opendental.com/api/v1/appointments?Limit=50&dateStart=2020-10-27&dateEnd=2022-02-09'+'&DateTStamp='+$json[\"DateTStamp\"]\n\nif (!items[0].json.offset) {\n  offset = 0\n} else\noffset = items[0].json.offset;\n\nreturn [\n    {\n      json: {\n        url : baseurl+'&Offset='+offset.toString(),\n        offset: items[0].json.offset\n      }\n    }\n]"
      },
      "name": "Create URL",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        1060,
        320
      ]
    },
    {
      "parameters": {
        "authentication": "headerAuth",
        "url": "={{$node[\"Functionx\"].json[\"url\"]}}{{$json[\"url\"]}}",
        "options": {
          "splitIntoItems": true
        }
      },
      "name": "OD API Get multiple",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 1,
      "position": [
        1280,
        320
      ],
      "alwaysOutputData": false,
      "notesInFlow": true,
      "credentials": {
        "httpHeaderAuth": {
          "id": "5",
          "name": "Header Auth KL Office"
        }
      },
      "notes": "For production use Date_Stop and DateTStamp in query."
    },
    {
      "parameters": {
        "conditions": {
          "number": [
            {
              "value1": "={{$items().length}}",
              "value2": 50
            }
          ]
        }
      },
      "name": "Loop on pagination",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        1500,
        320
      ]
    },
    {
      "parameters": {
        "keepOnlySet": true,
        "values": {
          "number": [
            {
              "name": "offset",
              "value": "={{($runIndex+1)*50}}"
            }
          ],
          "string": [
            {
              "name": "DateTStamp",
              "value": "={{$node[\"Merge\"].json[\"DateTStamp\"]}}"
            },
            {
              "name": "DateToday",
              "value": "={{$node[\"Merge\"].json[\"DateToday\"]}}"
            }
          ]
        },
        "options": {}
      },
      "name": "Set Offset&Save Request parameters",
      "type": "n8n-nodes-base.set",
      "typeVersion": 1,
      "position": [
        1760,
        480
      ],
      "executeOnce": true
    },
    {
      "parameters": {
        "functionCode": "const allData = []\n\nlet counter = 0;\ndo {\n  try {\n    const aja = $items(\"OD API Get multiple\",0,counter);                   \n    allData.push.apply(allData, aja);\n  } catch (error) {\n    return allData;  \n  }\n  counter++;\n} while(true);\n\n"
      },
      "name": "Merge Pagination Data",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        1760,
        300
      ]
    },
    {
      "parameters": {
        "authentication": "headerAuth",
        "url": "=https://api.opendental.com/api/v1/patients/{{$json[\"PatNum\"]}}",
        "options": {
          "splitIntoItems": false
        }
      },
      "name": "OD Get Patient Info by ID",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 1,
      "position": [
        2900,
        480
      ],
      "alwaysOutputData": false,
      "credentials": {
        "httpHeaderAuth": {
          "id": "5",
          "name": "Header Auth KL Office"
        }
      },
      "continueOnFail": true
    },
    {
      "parameters": {
        "mode": "mergeByIndex"
      },
      "name": "Merge Apts and Patient Info",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 1,
      "position": [
        3100,
        300
      ]
    },
    {
      "parameters": {
        "functionCode": "// Code here will run once per input item.\n// More info and help: https://docs.n8n.io/nodes/n8n-nodes-base.functionItem\nconst moment = require('moment');\n\n// convert Appointement Time with local timezone to ISO UTC\nvar AptStartLocalTZ = moment.tz($json[\"AptDateTime\"], $env['GENERIC_TIMEZONE']);\nitem.AptStartISOUTC = new Date(AptStartLocalTZ);\n\n// calculate Appointement Ending Time in UTC\nitem.AptEndISOUTC = new Date(item.AptStartISOUTC);\nitem.AptEndISOUTC.setSeconds(item.AptEndISOUTC.getSeconds() + (5*60*$json[\"Pattern\"].length));\n\n// create Opaque Google id\nitem.AptGoogleID = \"opendent\"+$json[\"AptNum\"];\n\nreturn item;"
      },
      "name": "OD Apt to Google Format",
      "type": "n8n-nodes-base.functionItem",
      "typeVersion": 1,
      "position": [
        3320,
        300
      ]
    },
    {
      "parameters": {
        "keepOnlySet": true,
        "values": {
          "string": [
            {
              "name": "DateTStamp",
              "value": "={{$json[\"DateTStamp\"]}}"
            }
          ]
        },
        "options": {
          "dotNotation": true
        }
      },
      "name": "Select latest DateTStamp",
      "type": "n8n-nodes-base.set",
      "typeVersion": 1,
      "position": [
        2200,
        300
      ],
      "executeOnce": true
    },
    {
      "parameters": {
        "fileName": "/home/node/.n8n/od_sync/timestamp_OD.bin"
      },
      "name": "Write Binary File OD Timestamp",
      "type": "n8n-nodes-base.writeBinaryFile",
      "typeVersion": 1,
      "position": [
        2640,
        300
      ],
      "executeOnce": true
    },
    {
      "parameters": {
        "operation": "removeDuplicates",
        "compare": "selectedFields",
        "fieldsToCompare": {
          "fields": [
            {
              "fieldName": "AptNum"
            }
          ]
        },
        "options": {
          "removeOtherFields": false
        }
      },
      "name": "Remove Duplicate AptNum",
      "type": "n8n-nodes-base.itemLists",
      "typeVersion": 1,
      "position": [
        2200,
        480
      ]
    },
    {
      "parameters": {
        "keys": {
          "key": [
            {
              "currentKey": "DateTStamp",
              "newKey": "LastAccess_DateTStamp"
            }
          ]
        }
      },
      "name": "Rename Keys",
      "type": "n8n-nodes-base.renameKeys",
      "typeVersion": 1,
      "position": [
        860,
        140
      ],
      "notesInFlow": true,
      "notes": "Do not rename, used by SetLastaccess on all items."
    },
    {
      "parameters": {
        "functionCode": "// Code here will run only once, no matter how many input items there are.\n// More info and help: https://docs.n8n.io/nodes/n8n-nodes-base.function\n\n\nfor (var i = 0; i < items.length;) {\n  if ((items[i].json.LastAccess_DateTStamp)==(items[i].json.DateTStamp)) {\n    items.splice(i,1);\n  } \n    else i++;\n}\n\nreturn items;\n"
      },
      "name": "Keep only new items",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        2640,
        480
      ]
    },
    {
      "parameters": {
        "values": {
          "string": [
            {
              "name": "LastAccess_DateTStamp",
              "value": "={{$item(0).$node[\"Rename Keys\"].json[\"LastAccess_DateTStamp\"]}}"
            }
          ]
        },
        "options": {}
      },
      "name": "Set LastAccess on all items",
      "type": "n8n-nodes-base.set",
      "typeVersion": 1,
      "position": [
        2420,
        480
      ]
    }
  ],
  "connections": {
    "Start": {
      "main": [
        [
          {
            "node": "Read Apt DateTStamp last access",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Apt DateTStamp last access": {
      "main": [
        [
          {
            "node": "Convert to JSON",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Convert to JSON": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          },
          {
            "node": "Rename Keys",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge": {
      "main": [
        [
          {
            "node": "Create URL",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Today": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Move Binary Data": {
      "main": [
        [
          {
            "node": "Write Binary File OD Timestamp",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sort by DateTStamp": {
      "main": [
        [
          {
            "node": "Select latest DateTStamp",
            "type": "main",
            "index": 0
          },
          {
            "node": "Remove Duplicate AptNum",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create URL": {
      "main": [
        [
          {
            "node": "OD API Get multiple",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OD API Get multiple": {
      "main": [
        [
          {
            "node": "Loop on pagination",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop on pagination": {
      "main": [
        [
          {
            "node": "Merge Pagination Data",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Set Offset&Save Request parameters",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Offset&Save Request parameters": {
      "main": [
        [
          {
            "node": "Create URL",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Pagination Data": {
      "main": [
        [
          {
            "node": "Sort by DateTStamp",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OD Get Patient Info by ID": {
      "main": [
        [
          {
            "node": "Merge Apts and Patient Info",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Merge Apts and Patient Info": {
      "main": [
        [
          {
            "node": "OD Apt to Google Format",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Select latest DateTStamp": {
      "main": [
        [
          {
            "node": "Move Binary Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Remove Duplicate AptNum": {
      "main": [
        [
          {
            "node": "Set LastAccess on all items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Keep only new items": {
      "main": [
        [
          {
            "node": "OD Get Patient Info by ID",
            "type": "main",
            "index": 0
          },
          {
            "node": "Merge Apts and Patient Info",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set LastAccess on all items": {
      "main": [
        [
          {
            "node": "Keep only new items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
2nd snipped: Update Google Calendar:

Code: Select all

{
  "nodes": [
    {
      "parameters": {},
      "name": "Start",
      "type": "n8n-nodes-base.start",
      "typeVersion": 1,
      "position": [
        220,
        300
      ]
    },
    {
      "parameters": {
        "operation": "get",
        "calendar": "srmgug2008lngoep8d3o49fcmg@group.calendar.google.com",
        "eventId": "={{$node[\"Save ID\"].json[\"AptGoogleID\"]}}",
        "options": {}
      },
      "name": "Google Calendar Get Event By ID",
      "type": "n8n-nodes-base.googleCalendar",
      "typeVersion": 1,
      "position": [
        900,
        60
      ],
      "alwaysOutputData": false,
      "credentials": {
        "googleCalendarOAuth2Api": {
          "id": "6",
          "name": "Google Calendar Judith Yang"
        }
      },
      "continueOnFail": true
    },
    {
      "parameters": {
        "calendar": "srmgug2008lngoep8d3o49fcmg@group.calendar.google.com",
        "start": "={{$json[\"AptStartISOUTC\"]}}",
        "end": "={{$json[\"AptEndISOUTC\"]}}",
        "additionalFields": {
          "attendees": [],
          "description": "=Procedure: {{$json[\"ProcDescript\"]}}",
          "id": "={{$json[\"AptGoogleID\"]}}",
          "summary": "={{$json[\"AptDateTime\"].substring(11,16)}} {{$json[\"FName\"]}} {{$json[\"LName\"].substring(0,1)}}. {{$json[\"Note\"].substring(0,700)}}"
        }
      },
      "name": "Google Calendar Create Event",
      "type": "n8n-nodes-base.googleCalendar",
      "typeVersion": 1,
      "position": [
        2000,
        120
      ],
      "retryOnFail": true,
      "waitBetweenTries": 5000,
      "credentials": {
        "googleCalendarOAuth2Api": {
          "id": "6",
          "name": "Google Calendar Judith Yang"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{$json[\"status\"]}}",
              "value2": "cancelled"
            }
          ]
        }
      },
      "name": "Google Calendar Event cancelled?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        1560,
        260
      ]
    },
    {
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{$json[\"id\"]}}",
              "operation": "isEmpty"
            }
          ]
        }
      },
      "name": "Google Calendar Event non-existent?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        1780,
        280
      ]
    },
    {
      "parameters": {
        "mode": "mergeByIndex"
      },
      "name": "Merge Google Events and OD Apts",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 1,
      "position": [
        900,
        280
      ]
    },
    {
      "parameters": {
        "batchSize": 1,
        "options": {}
      },
      "name": "SplitInBatches",
      "type": "n8n-nodes-base.splitInBatches",
      "typeVersion": 1,
      "position": [
        1120,
        280
      ],
      "notesInFlow": true,
      "notes": "Don't rename / Used by \"If\""
    },
    {
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": true,
              "value2": "={{$node[\"SplitInBatches\"].context[\"noItemsLeft\"]}}"
            }
          ]
        }
      },
      "name": "IF items left",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        2400,
        300
      ]
    },
    {
      "parameters": {},
      "name": "Save ID",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        640,
        300
      ]
    },
    {
      "parameters": {
        "workflowId": "10"
      },
      "name": "Retrieve Apt Update",
      "type": "n8n-nodes-base.executeWorkflow",
      "typeVersion": 1,
      "position": [
        440,
        300
      ]
    },
    {
      "parameters": {
        "triggerTimes": {
          "item": [
            {
              "mode": "everyX",
              "value": 5,
              "unit": "minutes"
            }
          ]
        }
      },
      "name": "Cron",
      "type": "n8n-nodes-base.cron",
      "typeVersion": 1,
      "position": [
        220,
        60
      ]
    },
    {
      "parameters": {
        "conditions": {
          "number": [
            {
              "value1": "={{$json[\"ProvNum\"]}}",
              "operation": "equal",
              "value2": 1
            }
          ]
        }
      },
      "name": "Select Provider 1",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        1300,
        280
      ]
    },
    {
      "parameters": {
        "operation": "update",
        "calendar": "srmgug2008lngoep8d3o49fcmg@group.calendar.google.com",
        "eventId": "={{$json[\"id\"]}}",
        "updateFields": {
          "description": "=Procedure: {{$json[\"ProcDescript\"]}}",
          "end": "={{$json[\"AptEndISOUTC\"]}}",
          "start": "={{$json[\"AptStartISOUTC\"]}}",
          "summary": "={{$json[\"AptDateTime\"].substring(11,16)}} {{$json[\"FName\"]}} {{$json[\"LName\"].substring(0,1)}}. {{$json[\"Note\"].substring(0,700)}}"
        }
      },
      "name": "Google Calendar Update Event (\"touch\")",
      "type": "n8n-nodes-base.googleCalendar",
      "typeVersion": 1,
      "position": [
        2000,
        300
      ],
      "retryOnFail": true,
      "waitBetweenTries": 5000,
      "credentials": {
        "googleCalendarOAuth2Api": {
          "id": "6",
          "name": "Google Calendar Judith Yang"
        }
      }
    },
    {
      "parameters": {
        "authentication": "oAuth2",
        "requestMethod": "PATCH",
        "url": "=https://www.googleapis.com/calendar/v3/calendars/srmgug2008lngoep8d3o49fcmg@group.calendar.google.com/events/{{$json[\"id\"]}}",
        "options": {},
        "bodyParametersUi": {
          "parameter": [
            {
              "name": "status",
              "value": "confirmed"
            }
          ]
        }
      },
      "name": "Try undo event cancellation",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 1,
      "position": [
        2220,
        -60
      ],
      "retryOnFail": true,
      "waitBetweenTries": 5000,
      "credentials": {
        "oAuth2Api": {
          "id": "7",
          "name": "Google Judith Yang"
        }
      },
      "notes": "https://stackoverflow.com/questions/18188681/undeleting-google-calendar-event/71283066#71283066\n\nThe patch operation on the status property seems to fail after deleting/cancelling an event with a 403 error on events that have been deleted some time ago. Setting the status to \"cancelled\" or deleting the event and then followed by setting the status to \"confirmed\" worked for me. This behavior is in accordance with the API documentation: \"Such cancelled events will eventually disappear, so do not rely on them being available indefinitely. Deleted events are only guaranteed to have the id field populated.\"."
    },
    {
      "parameters": {
        "command": "cp /home/node/.n8n/od_sync/timestamp_OD.bin /home/node/.n8n/od_sync/timestamp.bin"
      },
      "name": "Update last Apt DateTStamp",
      "type": "n8n-nodes-base.executeCommand",
      "typeVersion": 1,
      "position": [
        2620,
        280
      ]
    },
    {
      "parameters": {
        "conditions": {
          "number": [
            {
              "value1": "={{$json[\"Op\"]}}",
              "operation": "notEqual"
            }
          ],
          "string": [
            {
              "value1": "={{$json[\"AptStatus\"]}}",
              "operation": "notEqual",
              "value2": "Broken"
            }
          ]
        }
      },
      "name": "OD Apt really not cancelled?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        2000,
        -80
      ]
    }
  ],
  "connections": {
    "Start": {
      "main": [
        [
          {
            "node": "Retrieve Apt Update",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Calendar Get Event By ID": {
      "main": [
        [
          {
            "node": "Merge Google Events and OD Apts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Calendar Create Event": {
      "main": [
        [
          {
            "node": "IF items left",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Calendar Event cancelled?": {
      "main": [
        [
          {
            "node": "OD Apt really not cancelled?",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Google Calendar Event non-existent?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Calendar Event non-existent?": {
      "main": [
        [
          {
            "node": "Google Calendar Create Event",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Google Calendar Update Event (\"touch\")",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Google Events and OD Apts": {
      "main": [
        [
          {
            "node": "SplitInBatches",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "SplitInBatches": {
      "main": [
        [
          {
            "node": "Select Provider 1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF items left": {
      "main": [
        [
          {
            "node": "Update last Apt DateTStamp",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "SplitInBatches",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save ID": {
      "main": [
        [
          {
            "node": "Merge Google Events and OD Apts",
            "type": "main",
            "index": 1
          },
          {
            "node": "Google Calendar Get Event By ID",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Retrieve Apt Update": {
      "main": [
        [
          {
            "node": "Save ID",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Cron": {
      "main": [
        [
          {
            "node": "Retrieve Apt Update",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Select Provider 1": {
      "main": [
        [
          {
            "node": "Google Calendar Event cancelled?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Calendar Update Event (\"touch\")": {
      "main": [
        [
          {
            "node": "IF items left",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try undo event cancellation": {
      "main": [
        [
          {
            "node": "IF items left",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OD Apt really not cancelled?": {
      "main": [
        [
          {
            "node": "Try undo event cancellation",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "IF items left",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
3rd snipped: Delete Google calendar entries.

Code: Select all

{
  "nodes": [
    {
      "parameters": {},
      "name": "Start",
      "type": "n8n-nodes-base.start",
      "typeVersion": 1,
      "position": [
        -640,
        520
      ]
    },
    {
      "parameters": {
        "authentication": "headerAuth",
        "url": "=https://api.opendental.com/api/v1/appointments/{{$json[\"GoogleID\"]}}",
        "options": {}
      },
      "name": "OD Apt Get by ID",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 1,
      "position": [
        860,
        140
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "5",
          "name": "Header Auth KL Office"
        }
      },
      "continueOnFail": true
    },
    {
      "parameters": {
        "operation": "getAll",
        "calendar": "srmgug2008lngoep8d3o49fcmg@group.calendar.google.com",
        "returnAll": true,
        "options": {
          "updatedMin": "={{$json[\"DateTStamp\"]}}"
        }
      },
      "name": "Google Calendar Get All Events updated since yesterday",
      "type": "n8n-nodes-base.googleCalendar",
      "typeVersion": 1,
      "position": [
        440,
        320
      ],
      "notesInFlow": false,
      "credentials": {
        "googleCalendarOAuth2Api": {
          "id": "6",
          "name": "Google Calendar Judith Yang"
        }
      }
    },
    {
      "parameters": {
        "mode": "mergeByIndex"
      },
      "name": "Merge OD Apt and Google Event Info",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 1,
      "position": [
        1080,
        300
      ]
    },
    {
      "parameters": {
        "operation": "delete",
        "calendar": "srmgug2008lngoep8d3o49fcmg@group.calendar.google.com",
        "eventId": "={{$json[\"id\"]}}",
        "options": {}
      },
      "name": "Google Calendar Delete Event by ID",
      "type": "n8n-nodes-base.googleCalendar",
      "typeVersion": 1,
      "position": [
        2000,
        340
      ],
      "credentials": {
        "googleCalendarOAuth2Api": {
          "id": "6",
          "name": "Google Calendar Judith Yang"
        }
      },
      "continueOnFail": true
    },
    {
      "parameters": {
        "filePath": "/home/node/.n8n/od_sync/timestamp_google.bin"
      },
      "name": "Read Apt DateTStamp last access",
      "type": "n8n-nodes-base.readBinaryFile",
      "typeVersion": 1,
      "position": [
        -420,
        520
      ]
    },
    {
      "parameters": {
        "functionCode": "// Prepare for OD Database query\n\nfor (var i = 0; i < items.length;) {\n  // Save last access date in every item\n  items[i].json.DateTStamp =$node[\"Convert to JSON\"].json[\"DateTStamp\"];\n\n  // only touch opendent items \n  if (!items[i].json.id.startsWith('opendent')) {\n    items.splice(i,1);\n  } else {\n    // remove opendental-prefix from id\n    items[i].json.GoogleID = parseInt(items[i].json.id.replace('opendent',''));\n    // remove if already updated or invalid opendental-id (NaN)\n//    if ((((items[0].json.DateTStamp)==(items[0].json.updated)))||isNaN(items[i].json.GoogleID)) {\n    if (isNaN(items[i].json.GoogleID)) {\n        items.splice(i,1);\n    }\n    else i++;\n  }\n}\n\nreturn items;"
      },
      "name": "Delete and Trim Apt ID Prefix, remove cancelled Google Events",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        660,
        320
      ]
    },
    {
      "parameters": {
        "mode": "jsonToBinary",
        "options": {
          "mimeType": "application/json"
        }
      },
      "name": "Move Binary Data",
      "type": "n8n-nodes-base.moveBinaryData",
      "typeVersion": 1,
      "position": [
        2860,
        240
      ]
    },
    {
      "parameters": {
        "keepOnlySet": true,
        "values": {
          "string": [
            {
              "name": "DateTStamp",
              "value": "={{$json[\"DateTStamp\"]}}"
            }
          ]
        },
        "options": {
          "dotNotation": true
        }
      },
      "name": "Select latest DateTStamp",
      "type": "n8n-nodes-base.set",
      "typeVersion": 1,
      "position": [
        2640,
        240
      ],
      "executeOnce": true
    },
    {
      "parameters": {
        "fileName": "/home/node/.n8n/od_sync/timestamp_google.bin"
      },
      "name": "Write Binary File OD Timestamp",
      "type": "n8n-nodes-base.writeBinaryFile",
      "typeVersion": 1,
      "position": [
        3080,
        240
      ],
      "executeOnce": true
    },
    {
      "parameters": {
        "operation": "sort",
        "sortFieldsUi": {
          "sortField": [
            {
              "fieldName": "DateTStamp",
              "order": "descending"
            }
          ]
        },
        "options": {}
      },
      "name": "Item Lists",
      "type": "n8n-nodes-base.itemLists",
      "typeVersion": 1,
      "position": [
        2420,
        240
      ]
    },
    {
      "parameters": {
        "options": {}
      },
      "name": "Convert to JSON",
      "type": "n8n-nodes-base.moveBinaryData",
      "typeVersion": 1,
      "position": [
        -200,
        520
      ],
      "notesInFlow": true,
      "notes": "Don't change Name - required by Function Node"
    },
    {
      "parameters": {
        "action": "calculate",
        "value": "={{ new Date(new Date().getTime()) }}",
        "operation": "subtract",
        "duration": 1,
        "timeUnit": "months",
        "dataPropertyName": "DateTStamp_Earliest",
        "options": {}
      },
      "name": "Today - 1 month",
      "type": "n8n-nodes-base.dateTime",
      "typeVersion": 1,
      "position": [
        -420,
        360
      ]
    },
    {
      "parameters": {
        "mode": "mergeByIndex"
      },
      "name": "Merge",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 1,
      "position": [
        20,
        500
      ]
    },
    {
      "parameters": {
        "interval": 5,
        "unit": "minutes"
      },
      "name": "Interval",
      "type": "n8n-nodes-base.interval",
      "typeVersion": 1,
      "position": [
        -640,
        360
      ]
    },
    {
      "parameters": {
        "conditions": {
          "dateTime": [
            {
              "value1": "={{$json[\"DateTStamp\"]}}",
              "value2": "={{$json[\"DateTStamp_Earliest\"]}}"
            }
          ]
        }
      },
      "name": "IF",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        180,
        340
      ]
    },
    {
      "parameters": {
        "mode": "jsonToBinary",
        "convertAllData": false,
        "sourceKey": "={{$json[\"DateTStamp_Earliest\"]}}",
        "options": {
          "mimeType": "application/json"
        }
      },
      "name": "Move Binary Data1",
      "type": "n8n-nodes-base.moveBinaryData",
      "typeVersion": 1,
      "position": [
        440,
        540
      ]
    },
    {
      "parameters": {
        "fileName": "/home/node/.n8n/od_sync/timestamp_google.bin"
      },
      "name": "Write Binary File OD Timestamp1",
      "type": "n8n-nodes-base.writeBinaryFile",
      "typeVersion": 1,
      "position": [
        660,
        540
      ],
      "executeOnce": true
    },
    {
      "parameters": {
        "authentication": "oAuth2",
        "requestMethod": "PATCH",
        "url": "=https://www.googleapis.com/calendar/v3/calendars/srmgug2008lngoep8d3o49fcmg@group.calendar.google.com/events/{{$json[\"id\"]}}",
        "options": {},
        "bodyParametersUi": {
          "parameter": [
            {
              "name": "status",
              "value": "confirmed"
            }
          ]
        }
      },
      "name": "Try undelete cancelled events",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 1,
      "position": [
        1980,
        40
      ],
      "credentials": {
        "oAuth2Api": {
          "id": "7",
          "name": "Google Judith Yang"
        }
      },
      "continueOnFail": true,
      "notes": "https://stackoverflow.com/questions/18188681/undeleting-google-calendar-event/71283066#71283066\n\nThe patch operation on the status property seems to fail after deleting/cancelling an event with a 403 error on events that have been deleted some time ago. Setting the status to \"cancelled\" or deleting the event and then followed by setting the status to \"confirmed\" worked for me. This behavior is in accordance with the API documentation: \"Such cancelled events will eventually disappear, so do not rely on them being available indefinitely. Deleted events are only guaranteed to have the id field populated.\"."
    },
    {
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{$json[\"status\"]}}",
              "value2": "=cancelled"
            }
          ]
        },
        "combineOperation": "any"
      },
      "name": "Google Event Cancelled?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        1760,
        400
      ]
    },
    {
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{$json[\"GoogleID\"]}}",
              "value2": "={{$json[\"AptNum\"]}}"
            }
          ]
        }
      },
      "name": "Google Event and OD Apt entry??",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        1300,
        300
      ]
    },
    {
      "parameters": {
        "dataType": "string",
        "value1": "={{$json[\"AptStatus\"]}}",
        "rules": {
          "rules": [
            {
              "value2": "Scheduled"
            },
            {
              "value2": "Broken",
              "output": 1
            },
            {
              "value2": "cancelled",
              "output": 2
            }
          ]
        }
      },
      "name": "OD Apt Scheduled, Broken or cancelled?",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 1,
      "position": [
        1540,
        220
      ]
    },
    {
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{$json[\"status\"]}}",
              "value2": "=cancelled"
            }
          ]
        },
        "combineOperation": "any"
      },
      "name": "Google Event cancelled?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        1760,
        140
      ]
    },
    {
      "parameters": {
        "keys": {
          "key": [
            {
              "currentKey": "updated",
              "newKey": "DateTStamp"
            }
          ]
        }
      },
      "name": "Rename update to DateTStamp",
      "type": "n8n-nodes-base.renameKeys",
      "typeVersion": 1,
      "position": [
        2200,
        240
      ]
    }
  ],
  "connections": {
    "Start": {
      "main": [
        [
          {
            "node": "Read Apt DateTStamp last access",
            "type": "main",
            "index": 0
          },
          {
            "node": "Today - 1 month",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OD Apt Get by ID": {
      "main": [
        [
          {
            "node": "Merge OD Apt and Google Event Info",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Calendar Get All Events updated since yesterday": {
      "main": [
        [
          {
            "node": "Delete and Trim Apt ID Prefix, remove cancelled Google Events",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge OD Apt and Google Event Info": {
      "main": [
        [
          {
            "node": "Google Event and OD Apt entry??",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Apt DateTStamp last access": {
      "main": [
        [
          {
            "node": "Convert to JSON",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Delete and Trim Apt ID Prefix, remove cancelled Google Events": {
      "main": [
        [
          {
            "node": "OD Apt Get by ID",
            "type": "main",
            "index": 0
          },
          {
            "node": "Merge OD Apt and Google Event Info",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Move Binary Data": {
      "main": [
        [
          {
            "node": "Write Binary File OD Timestamp",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Select latest DateTStamp": {
      "main": [
        [
          {
            "node": "Move Binary Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Item Lists": {
      "main": [
        [
          {
            "node": "Select latest DateTStamp",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Convert to JSON": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Today - 1 month": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge": {
      "main": [
        [
          {
            "node": "IF",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Interval": {
      "main": [
        [
          {
            "node": "Read Apt DateTStamp last access",
            "type": "main",
            "index": 0
          },
          {
            "node": "Today - 1 month",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF": {
      "main": [
        [
          {
            "node": "Google Calendar Get All Events updated since yesterday",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Move Binary Data1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Move Binary Data1": {
      "main": [
        [
          {
            "node": "Write Binary File OD Timestamp1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try undelete cancelled events": {
      "main": [
        [
          {
            "node": "Rename update to DateTStamp",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Event Cancelled?": {
      "main": [
        null,
        [
          {
            "node": "Google Calendar Delete Event by ID",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Event and OD Apt entry??": {
      "main": [
        [
          {
            "node": "OD Apt Scheduled, Broken or cancelled?",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Google Event Cancelled?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OD Apt Scheduled, Broken or cancelled?": {
      "main": [
        [
          {
            "node": "Google Event cancelled?",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Google Calendar Delete Event by ID",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Google Calendar Delete Event by ID",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Event cancelled?": {
      "main": [
        [
          {
            "node": "Try undelete cancelled events",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Rename update to DateTStamp",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Rename update to DateTStamp": {
      "main": [
        [
          {
            "node": "Item Lists",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
[/quote]
Jordan Sparks, DMD
http://www.opendental.com

joergzastrau
Posts: 46
Joined: Sun Feb 27, 2022 2:53 am

Re: Schedules with google

Post by joergzastrau » Fri Mar 25, 2022 7:53 am

Edited 3x, include setup instructions, Errata as of 03/27/22

Hi Jordan,

I am sorry that the code is in a state that I don't consider ready for publication. However, please review and improve.

Walkthrough to get this working:


Install and setup N8N.io

Download n8n Desktop App from http://n8n.io (or install the docker image). In this post I used n8n Version 1.3.0 (MAC) and OD 21.3 and 21.4.

Start&Quit n8n once to create the configuration "home" folder. This folder contains the file n8n-desktop.env (e.g. ~/.n8n on mac/Linux).
Set the environment variable “NODE_FUNCTION_ALLOW_EXTERNAL=moment” by editing the file n8n-desktop.env to enable the supplied moment.js.


Retrieve Appointments from Open Dental

Setup Open Dental API Access

Copy&Paste the 1st snipped Code from posting #1 into n8n. Connect the Start Node. Select the first HTTP-Node “OD API Get multiple”. Select Credential for Header Auth, “Create New”.

Put in “Authorization” under Name and "ODFHIR {developerKey}/{customerAPIKey}" under Value.

Customize the workflow

Note 1: Currently the last access to Open Dental is stored in a file timestamp.bin, then updated in timestamp_OD which will be copied over timestamp.bin when everything went smooth. Unfortunately the way n8n works makes it hard to store temporary data. This is an area for improvement.

Put the Path to a file called timestamp.bin in “Read Apt DateTSTamp last access”, e.g. “/Users/joerg/Desktop/timestamp.bin” on a MAC. Put example content in timestamp.bin: {"DateTStamp":"2022-03-04 22:17:53”} in OD API format.
Edit the Node “Write Binary File OD Timestamp” and put in the path to timestamp_OD.bin, e.g. “/Users/joerg/Desktop/timestamp_OD.bin”.

Edit “Update last Apt DateTStamp” and correct the paths,
e.g. “cp /Users/judy/Desktop/timestamp_OD.bin /Users/judy/Desktop/timestamp.bin/“.

Execute the workflow. At this point you should be able to retrieve OD appointments.

Note 2: Open Dental allows for 64 Bit Integer as Appointment numbering. N8N does not (2^53 - 1).

Save your work.


Creating and updating Google Calendar events.

Note 3: Setting up Google OAuth is the hardest part. We do it twice here.

Setup Google Calendar Access
Copy&Paste the 2nd snipped Code from posting #1 into n8n. Connect the Start node to “Retrieve Apt Update”

In order to create credentials, select the “Calendar Get Event By ID” Node and select “new credentials”. Copy the OAuth Redirect URL

Sign up for the Google Cloud Platform
Create a Project by clicking on “Select a project” and “New Project”
Select a Project name “e.g. OD Calendar Sync” and click “Create”.
Select the project and goto “Api and Services”, “Enable APIS and Services”, select Google Calendar API and enable it.

From APIs and Services, Select OAuth consent Screen, make it an external Application and fill out the App information, skip “App domain” and “Authorized domain”, put in developer contact information and save.
Add scopes for Google Calendar API: …/auth/calendar.events, …/auth/calendar.events.owned and save.
Add a test user.

From APIs and Services, click Credentials and “Create Credentials”, Select OAuth client ID.
Select “Web application” as Application type and give it Name.
Paste the OAuth Redirect URL after clicking "ADD URI" and click "Create".

Copy the Client ID and Client Secret from Google Cloud Platform to the n8n Google Calendar node. Click “Sign in with Google” and select your calendar in the n8n node.

In the “Try undo event cancellation” node, select "Create new credentials".
Put in “Authorization URL”: “https://accounts.google.com/o/oauth2/v2/auth
Put in Access Token URL: “https://www.googleapis.com/oauth2/v4/token
Put in Client ID and Client Secret from above.
Put in Scope: “https://www.googleapis.com/auth/calendar.events
Put in Auth URI Query Parameters: “access_type=offline”
Put In Authentication: “Header”.
And click “Connect my account”.

Customize the workflow

Set the Workflow ID of the previously created Workflow to retrieve OD Appointments In “Retrieve Apt Update” (here: 1).
Edit “Update last Apt DateTStamp” and correct the paths, e.g. “cp /Users/joerg/Desktop/timestamp_OD.bin /Users/judy/Desktop/timestamp.bin/“

Execute the workflow. At this point you should be Create and Update Google Calendar entries from OD appointments for Provider Number 1 to Google Calendar.

Note 4: There are other areas for improvement. The current N8N.io node for Google Calendar Node is - old-style - (it doesn't use syncToken). Also the workflow only supports one provider and one google calendar but can be easily extended by Copy&Paste.

Save your work.


Delete Entries from Google Calendar

OLD: Copy&Paste the 3rd snipped Code from posting #1 into n8n. Connect the Start Node.

NEW: The 3rd snipped Workflow from posting #1 contains an error. Please Copy&Paste the following Workflow instead.

Code: Select all

{
  "nodes": [
    {
      "parameters": {},
      "name": "Start",
      "type": "n8n-nodes-base.start",
      "typeVersion": 1,
      "position": [
        240,
        300
      ]
    },
    {
      "parameters": {
        "authentication": "headerAuth",
        "url": "=https://api.opendental.com/api/v1/appointments/{{$json[\"GoogleID\"]}}",
        "options": {}
      },
      "name": "OD Apt Get by ID",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 1,
      "position": [
        1740,
        -80
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "3",
          "name": "Header Auth account"
        }
      },
      "continueOnFail": true
    },
    {
      "parameters": {
        "operation": "getAll",
        "calendar": "info@american-orthodontics.com",
        "returnAll": true,
        "options": {
          "updatedMin": "={{$json[\"DateTStamp\"]}}"
        }
      },
      "name": "Google Calendar Get All Events updated since yesterday",
      "type": "n8n-nodes-base.googleCalendar",
      "typeVersion": 1,
      "position": [
        1320,
        100
      ],
      "notesInFlow": false,
      "credentials": {
        "googleCalendarOAuth2Api": {
          "id": "2",
          "name": "info@american-orthodontics.com"
        }
      }
    },
    {
      "parameters": {
        "mode": "mergeByIndex"
      },
      "name": "Merge OD Apt and Google Event Info",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 1,
      "position": [
        1960,
        80
      ]
    },
    {
      "parameters": {
        "operation": "delete",
        "calendar": "srmgug2008lngoep8d3o49fcmg@group.calendar.google.com",
        "eventId": "={{$json[\"id\"]}}",
        "options": {}
      },
      "name": "Google Calendar Delete Event by ID",
      "type": "n8n-nodes-base.googleCalendar",
      "typeVersion": 1,
      "position": [
        2860,
        160
      ],
      "credentials": {
        "googleCalendarOAuth2Api": {
          "id": "2",
          "name": "info@american-orthodontics.com"
        }
      },
      "continueOnFail": true
    },
    {
      "parameters": {
        "filePath": "/Users/judy/Desktop/timestamp_google.bin"
      },
      "name": "Read Apt DateTStamp last access",
      "type": "n8n-nodes-base.readBinaryFile",
      "typeVersion": 1,
      "position": [
        460,
        300
      ]
    },
    {
      "parameters": {
        "functionCode": "// Prepare for OD Database query\n\nfor (var i = 0; i < items.length;) {\n  // Save last access date in every item\n  items[i].json.DateTStamp =$node[\"Convert to JSON\"].json[\"DateTStamp\"];\n\n  // only touch opendent items \n  if (!items[i].json.id.startsWith('opendent')) {\n    items.splice(i,1);\n  } else {\n    // remove opendental-prefix from id\n    items[i].json.GoogleID = parseInt(items[i].json.id.replace('opendent',''));\n    // remove if already updated or invalid opendental-id (NaN)\n//    if ((((items[0].json.DateTStamp)==(items[0].json.updated)))||isNaN(items[i].json.GoogleID)) {\n    if (isNaN(items[i].json.GoogleID)) {\n        items.splice(i,1);\n    }\n    else i++;\n  }\n}\n\nreturn items;"
      },
      "name": "Delete and Trim Apt ID Prefix, remove cancelled Google Events",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        1540,
        100
      ]
    },
    {
      "parameters": {
        "mode": "jsonToBinary",
        "options": {
          "mimeType": "application/json"
        }
      },
      "name": "Move Binary Data",
      "type": "n8n-nodes-base.moveBinaryData",
      "typeVersion": 1,
      "position": [
        3740,
        20
      ]
    },
    {
      "parameters": {
        "keepOnlySet": true,
        "values": {
          "string": [
            {
              "name": "DateTStamp",
              "value": "={{$json[\"DateTStamp\"]}}"
            }
          ]
        },
        "options": {
          "dotNotation": true
        }
      },
      "name": "Select latest DateTStamp",
      "type": "n8n-nodes-base.set",
      "typeVersion": 1,
      "position": [
        3520,
        20
      ],
      "executeOnce": true
    },
    {
      "parameters": {
        "fileName": "/Users/judy/Desktop/timestamp_google.bin"
      },
      "name": "Write Binary File OD Timestamp",
      "type": "n8n-nodes-base.writeBinaryFile",
      "typeVersion": 1,
      "position": [
        3960,
        20
      ],
      "executeOnce": true
    },
    {
      "parameters": {
        "operation": "sort",
        "sortFieldsUi": {
          "sortField": [
            {
              "fieldName": "DateTStamp",
              "order": "descending"
            }
          ]
        },
        "options": {}
      },
      "name": "Item Lists",
      "type": "n8n-nodes-base.itemLists",
      "typeVersion": 1,
      "position": [
        3300,
        20
      ]
    },
    {
      "parameters": {
        "options": {}
      },
      "name": "Convert to JSON",
      "type": "n8n-nodes-base.moveBinaryData",
      "typeVersion": 1,
      "position": [
        680,
        300
      ],
      "notesInFlow": true,
      "notes": "Don't change Name - required by Function Node"
    },
    {
      "parameters": {
        "action": "calculate",
        "value": "={{ new Date(new Date().getTime()) }}",
        "operation": "subtract",
        "duration": 1,
        "timeUnit": "months",
        "dataPropertyName": "DateTStamp_Earliest",
        "options": {}
      },
      "name": "Today - 1 month",
      "type": "n8n-nodes-base.dateTime",
      "typeVersion": 1,
      "position": [
        460,
        140
      ]
    },
    {
      "parameters": {
        "mode": "mergeByIndex"
      },
      "name": "Merge",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 1,
      "position": [
        900,
        280
      ]
    },
    {
      "parameters": {
        "interval": 5,
        "unit": "minutes"
      },
      "name": "Interval",
      "type": "n8n-nodes-base.interval",
      "typeVersion": 1,
      "position": [
        240,
        140
      ]
    },
    {
      "parameters": {
        "conditions": {
          "dateTime": [
            {
              "value1": "={{$json[\"DateTStamp\"]}}",
              "value2": "={{$json[\"DateTStamp_Earliest\"]}}"
            }
          ]
        }
      },
      "name": "IF",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        1060,
        120
      ]
    },
    {
      "parameters": {
        "mode": "jsonToBinary",
        "convertAllData": false,
        "sourceKey": "={{$json[\"DateTStamp_Earliest\"]}}",
        "options": {
          "mimeType": "application/json"
        }
      },
      "name": "Move Binary Data1",
      "type": "n8n-nodes-base.moveBinaryData",
      "typeVersion": 1,
      "position": [
        1320,
        320
      ]
    },
    {
      "parameters": {
        "fileName": "/home/node/.n8n/od_sync/timestamp_google.bin"
      },
      "name": "Write Binary File OD Timestamp1",
      "type": "n8n-nodes-base.writeBinaryFile",
      "typeVersion": 1,
      "position": [
        1540,
        320
      ],
      "executeOnce": true
    },
    {
      "parameters": {
        "authentication": "oAuth2",
        "requestMethod": "PATCH",
        "url": "=https://www.googleapis.com/calendar/v3/calendars/srmgug2008lngoep8d3o49fcmg@group.calendar.google.com/events/{{$json[\"id\"]}}",
        "options": {},
        "bodyParametersUi": {
          "parameter": [
            {
              "name": "status",
              "value": "confirmed"
            }
          ]
        }
      },
      "name": "Try undelete cancelled events",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 1,
      "position": [
        2860,
        -180
      ],
      "credentials": {
        "oAuth2Api": {
          "id": "4",
          "name": "Unnamed credential"
        }
      },
      "continueOnFail": true,
      "notes": "https://stackoverflow.com/questions/18188681/undeleting-google-calendar-event/71283066#71283066\n\nThe patch operation on the status property seems to fail after deleting/cancelling an event with a 403 error on events that have been deleted some time ago. Setting the status to \"cancelled\" or deleting the event and then followed by setting the status to \"confirmed\" worked for me. This behavior is in accordance with the API documentation: \"Such cancelled events will eventually disappear, so do not rely on them being available indefinitely. Deleted events are only guaranteed to have the id field populated.\"."
    },
    {
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{$json[\"status\"]}}",
              "value2": "=cancelled"
            }
          ]
        },
        "combineOperation": "any"
      },
      "name": "Google Event Cancelled?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        2640,
        140
      ]
    },
    {
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{$json[\"GoogleID\"]}}",
              "value2": "={{$json[\"AptNum\"]}}"
            }
          ]
        }
      },
      "name": "Google Event and OD Apt entry??",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        2180,
        80
      ]
    },
    {
      "parameters": {
        "dataType": "string",
        "value1": "={{$json[\"AptStatus\"]}}",
        "rules": {
          "rules": [
            {
              "value2": "Scheduled"
            },
            {
              "value2": "Broken",
              "output": 1
            },
            {
              "value2": "cancelled",
              "output": 2
            }
          ]
        }
      },
      "name": "OD Apt Scheduled, Broken or cancelled?",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 1,
      "position": [
        2420,
        0
      ]
    },
    {
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{$json[\"status\"]}}",
              "value2": "=cancelled"
            }
          ]
        },
        "combineOperation": "any"
      },
      "name": "Google Event cancelled?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        2640,
        -80
      ]
    },
    {
      "parameters": {
        "keys": {
          "key": [
            {
              "currentKey": "updated",
              "newKey": "DateTStamp"
            }
          ]
        }
      },
      "name": "Rename update to DateTStamp",
      "type": "n8n-nodes-base.renameKeys",
      "typeVersion": 1,
      "position": [
        3080,
        20
      ]
    }
  ],
  "connections": {
    "Start": {
      "main": [
        [
          {
            "node": "Read Apt DateTStamp last access",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OD Apt Get by ID": {
      "main": [
        [
          {
            "node": "Merge OD Apt and Google Event Info",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Calendar Get All Events updated since yesterday": {
      "main": [
        [
          {
            "node": "Delete and Trim Apt ID Prefix, remove cancelled Google Events",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge OD Apt and Google Event Info": {
      "main": [
        [
          {
            "node": "Google Event and OD Apt entry??",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Apt DateTStamp last access": {
      "main": [
        [
          {
            "node": "Convert to JSON",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Delete and Trim Apt ID Prefix, remove cancelled Google Events": {
      "main": [
        [
          {
            "node": "OD Apt Get by ID",
            "type": "main",
            "index": 0
          },
          {
            "node": "Merge OD Apt and Google Event Info",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Move Binary Data": {
      "main": [
        [
          {
            "node": "Write Binary File OD Timestamp",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Select latest DateTStamp": {
      "main": [
        [
          {
            "node": "Move Binary Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Item Lists": {
      "main": [
        [
          {
            "node": "Select latest DateTStamp",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Convert to JSON": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Today - 1 month": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge": {
      "main": [
        [
          {
            "node": "IF",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Interval": {
      "main": [
        [
          {
            "node": "Read Apt DateTStamp last access",
            "type": "main",
            "index": 0
          },
          {
            "node": "Today - 1 month",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF": {
      "main": [
        [
          {
            "node": "Google Calendar Get All Events updated since yesterday",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Move Binary Data1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Move Binary Data1": {
      "main": [
        [
          {
            "node": "Write Binary File OD Timestamp1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try undelete cancelled events": {
      "main": [
        [
          {
            "node": "Rename update to DateTStamp",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Event Cancelled?": {
      "main": [
        null,
        [
          {
            "node": "Google Calendar Delete Event by ID",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Event and OD Apt entry??": {
      "main": [
        [
          {
            "node": "OD Apt Scheduled, Broken or cancelled?",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Google Event Cancelled?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OD Apt Scheduled, Broken or cancelled?": {
      "main": [
        [
          {
            "node": "Google Event cancelled?",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Google Event Cancelled?",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Google Event Cancelled?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Event cancelled?": {
      "main": [
        [
          {
            "node": "Try undelete cancelled events",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Rename update to DateTStamp",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Rename update to DateTStamp": {
      "main": [
        [
          {
            "node": "Item Lists",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Select credentials in “Google Calendar Get All Events updated since yesterday”, “OD Apt Get by ID” and “Try undelete cancelled events”.

Edit “Read Apt DateTStamp last access” and. Put in correct path and create the file, e.g. “/Users/joerg/Desktop/timestamp_google.bin” with the DateTime in ISO format, e.g. "{"DateTStamp":"2022-03-24T13:49:18.544Z"}".

Save your work.


This is something that I did for a single OD installation and and my last bit of -real- coding was back in 2001 or something. Some of the code is taken from examples published on n8n.io, e.g. the pagination code and the idea for the loop.

Joerg

joergzastrau
Posts: 46
Joined: Sun Feb 27, 2022 2:53 am

Re: Schedules with google - Update 1/2 (using n8n) V2.x

Post by joergzastrau » Thu Aug 01, 2024 7:47 am

Dear all,

these are updates to the worklow.

Requirements are Open Dental 22.4.28
Tested with N8N Version 1.50.1
Google Calendar API V3

The posted code has served us now for more than 2 years. However, improvements to the APIs triggered an update.

There are basically now only 2 Worklows: One for adding/updating Open Dental Events in Google Calendar and one for the deletion of appointments in Google Calendar for each user. The workflows are still polling every 5min because it would not conserve significant resources to fire OD API events.

The workflows offload more processing to the OD and Google APIs, uses more functionalities from n8n nodes. The codes to "patch" Google events remains (however, I haven't seen it to be successful for some time - Google didn't promise it).

This is posting 1/2 (maximum of allowed characters exceeded).

Add Google Calendar Events (polling) V2.1

Code: Select all

{
  "meta": {
    "instanceId": "c5bbe0e4dd0c2a71ee01ba0477f0fe876e4ef1ddb06022d07739fda528d4f9f1"
  },
  "nodes": [
    {
      "parameters": {},
      "id": "43922602-6307-4577-ad02-20fbc359afa0",
      "name": "When clicking ‘Test workflow’",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        -2940,
        600
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes"
            }
          ]
        }
      },
      "id": "53e7ff75-4af6-401c-89c4-d9f49ead55ee",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        -2940,
        420
      ]
    },
    {
      "parameters": {
        "jsCode": "// Get the global workflow static data\nconst workflowStaticData = $getWorkflowStaticData('global');\n\nreturn {json:{\n\t\t\"lastaccess\": workflowStaticData.lastaccess,\n        \"offset\": 0\n  }\n}\n"
      },
      "id": "f00085ef-4cc6-4bff-a472-ea6ad6500892",
      "name": "Restore lastaccess variable",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -2740,
        420
      ]
    },
    {
      "parameters": {
        "value": "={{ new Date(new Date().getTime()) }}",
        "dataPropertyName": "DateToday",
        "toFormat": "YYYY-MM-DD",
        "options": {}
      },
      "name": "Today",
      "type": "n8n-nodes-base.dateTime",
      "typeVersion": 1,
      "position": [
        -2540,
        600
      ],
      "id": "68adfc56-a0b0-4808-a7e5-945d55a1b6a7"
    },
    {
      "parameters": {
        "keepOnlySet": true,
        "values": {
          "number": [
            {
              "name": "offset",
              "value": "={{($runIndex+1)*50}}"
            }
          ]
        },
        "options": {}
      },
      "name": "Update Pagination offset",
      "type": "n8n-nodes-base.set",
      "typeVersion": 1,
      "position": [
        -1480,
        760
      ],
      "executeOnce": true,
      "id": "a94f9646-b7e5-4616-8b0f-bd930f827ab6"
    },
    {
      "parameters": {
        "operation": "sort",
        "sortFieldsUi": {
          "sortField": [
            {
              "fieldName": "DateTStamp",
              "order": "descending"
            }
          ]
        },
        "options": {}
      },
      "name": "Sort by DateTStamp",
      "type": "n8n-nodes-base.itemLists",
      "typeVersion": 1,
      "position": [
        -1280,
        420
      ],
      "id": "7287d813-dd44-40de-88aa-c4d907524f96",
      "continueOnFail": true
    },
    {
      "parameters": {
        "value": "={{$json[\"AptDateTime\"]}}",
        "dataPropertyName": "AptStartISOUTC",
        "custom": true,
        "options": {
          "toTimezone": "UTC"
        }
      },
      "id": "1d8c764d-e650-4087-a5b7-dac8f431efe4",
      "name": "AptDateTime to UTC",
      "type": "n8n-nodes-base.dateTime",
      "typeVersion": 1,
      "position": [
        -1280,
        580
      ]
    },
    {
      "parameters": {
        "action": "calculate",
        "value": "={{$json[\"AptStartISOUTC\"]}}",
        "duration": "={{$json[\"Pattern\"].length*5*60}}",
        "timeUnit": "seconds",
        "dataPropertyName": "AptEndISOUTC",
        "options": {
          "fromFormat": ""
        }
      },
      "id": "8e379479-0fe6-4630-8b57-fbada6418bce",
      "name": "AptDateTimeEnd calculate in UTC",
      "type": "n8n-nodes-base.dateTime",
      "typeVersion": 1,
      "position": [
        -1080,
        580
      ]
    },
    {
      "parameters": {
        "authentication": "headerAuth",
        "url": "=http://192.168.1.60:30223/api/v1/patients/{{$json[\"PatNum\"]}}",
        "options": {
          "splitIntoItems": false
        }
      },
      "name": "OD Get Patient Info by ID",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 1,
      "position": [
        -2120,
        420
      ],
      "alwaysOutputData": false,
      "id": "59ab8330-8f51-4de9-9a47-3977edae4116",
      "credentials": {
        "httpHeaderAuth": {
          "id": "c8ZCHq07tFc7C8sw",
          "name": "Header Auth account KL Office"
        }
      },
      "continueOnFail": true
    },
    {
      "parameters": {
        "mode": "combine",
        "combineBy": "combineByPosition",
        "options": {}
      },
      "id": "b4230c70-98ae-4328-95f6-d711d0ff9618",
      "name": "Merge Apt and Patient data",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3,
      "position": [
        -1900,
        580
      ]
    },
    {
      "parameters": {
        "authentication": "headerAuth",
        "url": "=http://192.168.1.60:30223/api/v1/appointments",
        "options": {
          "splitIntoItems": true
        },
        "queryParametersUi": {
          "parameter": [
            {
              "name": "AptStatus",
              "value": "Scheduled"
            },
            {
              "name": "DateTStamp",
              "value": "={{ $json.lastaccess }}"
            },
            {
              "name": "dateStart",
              "value": "={{ $json.DateToday }}"
            },
            {
              "name": "Offset",
              "value": "={{ $json.offset }}"
            },
            {
              "name": "Limit",
              "value": "50"
            }
          ]
        }
      },
      "name": "OD API Get New Future Scheduled Appointments",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 1,
      "position": [
        -2320,
        600
      ],
      "alwaysOutputData": false,
      "notesInFlow": true,
      "id": "3c9c9988-406f-4ccd-870c-48fb46a09059",
      "credentials": {
        "httpHeaderAuth": {
          "id": "c8ZCHq07tFc7C8sw",
          "name": "Header Auth account KL Office"
        }
      },
      "notes": "For production use Date_Stop and DateTStamp in query."
    },
    {
      "parameters": {
        "mode": "combine",
        "combineBy": "combineByPosition",
        "options": {}
      },
      "id": "b33ca917-691d-4b6a-b920-7b159b0671df",
      "name": "Merge",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3,
      "position": [
        -2300,
        1080
      ]
    },
    {
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{$json[\"status\"]}}",
              "value2": "cancelled"
            }
          ]
        }
      },
      "name": "Google Calendar Event cancelled?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        -1900,
        1200
      ],
      "id": "433aae50-c09f-4305-a11e-2b87bacfb22f"
    },
    {
      "parameters": {},
      "id": "55057259-0851-47c6-a5aa-bfe8e69cbdc2",
      "name": "NOP - Got future OD appointments",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        -900,
        780
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Get the global workflow static data\nconst workflowStaticData = $getWorkflowStaticData('global');\n\n// Update its data\nworkflowStaticData.lastaccess = $json[\"lastaccess\"];\n\nreturn {json:{\n\t\t\"content\": workflowStaticData\n  }\n}\n"
      },
      "id": "fbc13e57-0409-461c-a254-4cc70d8794c3",
      "name": "update lastaccess variable",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -900,
        420
      ]
    },
    {
      "parameters": {
        "keepOnlySet": true,
        "values": {
          "string": [
            {
              "name": "lastaccess",
              "value": "={{$json[\"DateTStamp\"]}}"
            }
          ]
        },
        "options": {
          "dotNotation": true
        }
      },
      "name": "Select latest DateTStamp",
      "type": "n8n-nodes-base.set",
      "typeVersion": 1,
      "position": [
        -1080,
        420
      ],
      "executeOnce": true,
      "id": "dd5f46b7-b83a-4501-a67a-fd7b26d379ea"
    },
    {
      "parameters": {},
      "id": "ccf3f3b9-b021-41de-9a4a-19bd8fe40dbf",
      "name": "NOP - Distribute to Google Calendards",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        -2940,
        1080
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "c6e036ce-e611-4652-aa30-be041fd43a66",
              "leftValue": "={{ $json.id }}",
              "rightValue": "",
              "operator": {
                "type": "string",
                "operation": "empty",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "7e15c579-cfce-4455-b004-b5c85bc909e4",
      "name": "New Event found?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        -2100,
        1080
      ]
    },
    {
      "parameters": {
        "mode": "combine",
        "combineBy": "combineByPosition",
        "options": {}
      },
      "id": "010f62f2-e98a-493f-8511-7481b36e67ad",
      "name": "Merge1",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3,
      "position": [
        -2300,
        1660
      ]
    },
    {
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{$json[\"status\"]}}",
              "value2": "cancelled"
            }
          ]
        }
      },
      "name": "Google Calendar Event cancelled?1",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        -1900,
        1780
      ],
      "id": "efeaab2d-7f03-4885-ad84-04286ef6b24d"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "c6e036ce-e611-4652-aa30-be041fd43a66",
              "leftValue": "={{ $json.id }}",
              "rightValue": "",
              "operator": {
                "type": "string",
                "operation": "empty",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "79d844ec-4d5b-4fd7-9974-b0b21e5bd0f8",
      "name": "New Event found?1",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        -2100,
        1660
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "9aafa027-7142-4134-bbfe-b843edb47304",
              "name": "lastaccess",
              "value": "2024-07-30 02:45:00",
              "type": "string"
            },
            {
              "id": "2d39cdfa-f99d-4f09-a83c-a78572affac0",
              "name": "offset",
              "value": 0,
              "type": "number"
            }
          ]
        },
        "options": {}
      },
      "id": "4252a532-dcdf-44b7-95b0-cb4d3388f2a9",
      "name": "Test Data",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        -2740,
        600
      ]
    },
    {
      "parameters": {
        "conditions": {
          "number": [
            {
              "value1": "={{$items().length}}",
              "value2": 50
            }
          ]
        }
      },
      "name": "Loop on pagination",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        -1680,
        580
      ],
      "id": "b4164f32-c5b2-4558-a51a-edee6c8b1ece"
    },
    {
      "parameters": {
        "jsCode": "const allData = []\n\nlet counter = 0;\ndo {\n  try {\n    const aja = $items(\"Merge Apt and Patient data\",0,counter);                   \n    allData.push.apply(allData, aja);\n  } catch (error) {\n    return allData;  \n  }\n  counter++;\n} while(true);\n"
      },
      "id": "134a7a2f-23fe-4fa0-b304-9847d65cef2e",
      "name": "Merge paginated data",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -1480,
        580
      ]
    },
    {
      "parameters": {
        "authentication": "oAuth2",
        "requestMethod": "PATCH",
        "url": "=https://www.googleapis.com/calendar/v3/calendars/zastrau%40gmail.com/events/{{$json[\"id\"]}}",
        "options": {},
        "bodyParametersUi": {
          "parameter": [
            {
              "name": "status",
              "value": "confirmed"
            }
          ]
        }
      },
      "name": "Try undo event cancellation User  2",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 1,
      "position": [
        -1680,
        1760
      ],
      "retryOnFail": false,
      "waitBetweenTries": 5000,
      "id": "021dcd56-16df-4bdc-b779-0eb2f5b35ab7",
      "alwaysOutputData": true,
      "credentials": {
        "oAuth2Api": {
          "id": "f4pZB4Q3vvy7gzwF",
          "name": "Google Calendar Joerg Zastrau"
        }
      },
      "onError": "continueRegularOutput",
      "notes": "https://stackoverflow.com/questions/18188681/undeleting-google-calendar-event/71283066#71283066\n\nThe patch operation on the status property seems to fail after deleting/cancelling an event with a 403 error on events that have been deleted some time ago. Setting the status to \"cancelled\" or deleting the event and then followed by setting the status to \"confirmed\" worked for me. This behavior is in accordance with the API documentation: \"Such cancelled events will eventually disappear, so do not rely on them being available indefinitely. Deleted events are only guaranteed to have the id field populated.\"."
    },
    {
      "parameters": {
        "operation": "update",
        "calendar": {
          "__rl": true,
          "value": "zastrau@gmail.com",
          "mode": "list",
          "cachedResultName": "zastrau@gmail.com"
        },
        "eventId": "={{$json[\"id\"]}}",
        "updateFields": {
          "description": "=Procedure: {{$json[\"ProcDescript\"]}}",
          "end": "={{$json[\"AptEndISOUTC\"]}}",
          "start": "={{$json[\"AptStartISOUTC\"]}}",
          "summary": "={{$json[\"AptDateTime\"].substring(11,16)}} {{$json[\"FName\"]}} {{$json[\"LName\"].substring(0,1)}}. {{$json[\"Note\"].substring(0,700)}}"
        }
      },
      "name": "Google Calendar Update Event (\"touch\") User  2",
      "type": "n8n-nodes-base.googleCalendar",
      "typeVersion": 1,
      "position": [
        -1680,
        1960
      ],
      "retryOnFail": true,
      "waitBetweenTries": 5000,
      "id": "c8615476-fea7-47f5-ac4f-587a6d27d5a1",
      "credentials": {
        "googleCalendarOAuth2Api": {
          "id": "a12lZKdLn26CvrR2",
          "name": "Google Calendar account Zastrau"
        }
      },
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "calendar": {
          "__rl": true,
          "value": "=zastrau@gmail.com",
          "mode": "id",
          "__regex": "(^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*)"
        },
        "start": "={{$json[\"AptStartISOUTC\"]}}",
        "end": "={{$json[\"AptEndISOUTC\"]}}",
        "additionalFields": {
          "attendees": [],
          "description": "=Procedure: {{$json[\"ProcDescript\"]}}",
          "id": "=opendent{{ $json[\"AptNum\"] }}",
          "summary": "={{$json[\"AptDateTime\"].substring(11,16)}} {{$json[\"FName\"]}} {{$json[\"LName\"].substring(0,1)}}. {{$json[\"Note\"].substring(0,700)}}"
        }
      },
      "name": "Google Calendar Create Event User  2",
      "type": "n8n-nodes-base.googleCalendar",
      "typeVersion": 1,
      "position": [
        -1680,
        1580
      ],
      "retryOnFail": true,
      "waitBetweenTries": 5000,
      "id": "e47d1bbb-a426-424b-9df8-23dd2c5f6396",
      "credentials": {
        "googleCalendarOAuth2Api": {
          "id": "a12lZKdLn26CvrR2",
          "name": "Google Calendar account Zastrau"
        }
      },
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "operation": "update",
        "calendar": {
          "__rl": true,
          "value": "srmgug2008lngoep8d3o49fcmg@group.calendar.google.com",
          "mode": "list",
          "cachedResultName": "KL Office"
        },
        "eventId": "={{$json[\"id\"]}}",
        "updateFields": {
          "description": "=Procedure: {{$json[\"ProcDescript\"]}}",
          "end": "={{$json[\"AptEndISOUTC\"]}}",
          "start": "={{$json[\"AptStartISOUTC\"]}}",
          "summary": "={{$json[\"AptDateTime\"].substring(11,16)}} {{$json[\"FName\"]}} {{$json[\"LName\"].substring(0,1)}}. {{$json[\"Note\"].substring(0,700)}}"
        }
      },
      "name": "Google Calendar Update Event (\"touch\") User 1",
      "type": "n8n-nodes-base.googleCalendar",
      "typeVersion": 1,
      "position": [
        -1680,
        1380
      ],
      "retryOnFail": true,
      "waitBetweenTries": 5000,
      "id": "3e46a54b-00bb-42e2-9e0c-67230156a66f",
      "credentials": {
        "googleCalendarOAuth2Api": {
          "id": "T5W9zRGj16MupgOn",
          "name": "Google Calendar account Yang"
        }
      },
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "authentication": "oAuth2",
        "requestMethod": "PATCH",
        "url": "=https://www.googleapis.com/calendar/v3/calendars/srmgug2008lngoep8d3o49fcmg@group.calendar.google.com/events/{{$json[\"id\"]}}",
        "options": {},
        "bodyParametersUi": {
          "parameter": [
            {
              "name": "status",
              "value": "confirmed"
            }
          ]
        }
      },
      "name": "Try undo event cancellation User 1",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 1,
      "position": [
        -1680,
        1180
      ],
      "retryOnFail": false,
      "waitBetweenTries": 5000,
      "id": "d84a08e9-2e75-4d91-9251-b80ba52de528",
      "alwaysOutputData": true,
      "credentials": {
        "oAuth2Api": {
          "id": "rNMBNWmZs1mbgJwO",
          "name": "Google Calendar Account Yang"
        }
      },
      "onError": "continueRegularOutput",
      "notes": "https://stackoverflow.com/questions/18188681/undeleting-google-calendar-event/71283066#71283066\n\nThe patch operation on the status property seems to fail after deleting/cancelling an event with a 403 error on events that have been deleted some time ago. Setting the status to \"cancelled\" or deleting the event and then followed by setting the status to \"confirmed\" worked for me. This behavior is in accordance with the API documentation: \"Such cancelled events will eventually disappear, so do not rely on them being available indefinitely. Deleted events are only guaranteed to have the id field populated.\"."
    },
    {
      "parameters": {
        "calendar": {
          "__rl": true,
          "value": "srmgug2008lngoep8d3o49fcmg@group.calendar.google.com",
          "mode": "list",
          "cachedResultName": "KL Office"
        },
        "start": "={{$json[\"AptStartISOUTC\"]}}",
        "end": "={{$json[\"AptEndISOUTC\"]}}",
        "additionalFields": {
          "attendees": [],
          "description": "=Procedure: {{$json[\"ProcDescript\"]}}",
          "id": "=opendent{{ $json[\"AptNum\"] }}",
          "summary": "={{$json[\"AptDateTime\"].substring(11,16)}} {{$json[\"FName\"]}} {{$json[\"LName\"].substring(0,1)}}. {{$json[\"Note\"].substring(0,700)}}"
        }
      },
      "name": "Google Calendar Create Event User 1",
      "type": "n8n-nodes-base.googleCalendar",
      "typeVersion": 1,
      "position": [
        -1680,
        1000
      ],
      "retryOnFail": true,
      "waitBetweenTries": 5000,
      "id": "8430e48c-d0fa-4f2b-8d40-702a88cb7819",
      "credentials": {
        "googleCalendarOAuth2Api": {
          "id": "T5W9zRGj16MupgOn",
          "name": "Google Calendar account Yang"
        }
      },
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "operation": "get",
        "calendar": {
          "__rl": true,
          "value": "srmgug2008lngoep8d3o49fcmg@group.calendar.google.com",
          "mode": "list",
          "cachedResultName": "KL Office"
        },
        "eventId": "=opendent{{ $json[\"AptNum\"] }}",
        "options": {}
      },
      "id": "a701cf3e-1b24-4323-bac5-edad312a2627",
      "name": "Google Calendar of User 1",
      "type": "n8n-nodes-base.googleCalendar",
      "typeVersion": 1.1,
      "position": [
        -2520,
        1200
      ],
      "alwaysOutputData": true,
      "credentials": {
        "googleCalendarOAuth2Api": {
          "id": "T5W9zRGj16MupgOn",
          "name": "Google Calendar account Yang"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "conditions": {
          "number": [
            {
              "value1": "={{$json[\"ProvNum\"]}}",
              "operation": "equal",
              "value2": 1
            }
          ]
        }
      },
      "name": "Select Providers for User 1",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        -2740,
        1080
      ],
      "id": "d9255fcf-44aa-454e-8f6b-e0164d9c0cd4"
    },
    {
      "parameters": {
        "operation": "get",
        "calendar": {
          "__rl": true,
          "value": "zastrau@gmail.com",
          "mode": "list",
          "cachedResultName": "zastrau@gmail.com"
        },
        "eventId": "=opendent{{ $json[\"AptNum\"] }}",
        "options": {}
      },
      "id": "62041de0-ffea-4193-a401-fcc41ed3af30",
      "name": "Google Calendar of User 2",
      "type": "n8n-nodes-base.googleCalendar",
      "typeVersion": 1.1,
      "position": [
        -2520,
        1780
      ],
      "alwaysOutputData": true,
      "credentials": {
        "googleCalendarOAuth2Api": {
          "id": "a12lZKdLn26CvrR2",
          "name": "Google Calendar account Zastrau"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "conditions": {
          "number": [
            {
              "value1": "={{$json[\"ProvNum\"]}}",
              "operation": "equal",
              "value2": 1
            }
          ]
        }
      },
      "name": "Select Providers for User 2",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        -2740,
        1660
      ],
      "id": "7d41e8ac-a510-4900-a5e2-753b0baabafb"
    }
  ],
  "connections": {
    "When clicking ‘Test workflow’": {
      "main": [
        [
          {
            "node": "Test Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Restore lastaccess variable",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Restore lastaccess variable": {
      "main": [
        [
          {
            "node": "Today",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Today": {
      "main": [
        [
          {
            "node": "OD API Get New Future Scheduled Appointments",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Pagination offset": {
      "main": [
        [
          {
            "node": "OD API Get New Future Scheduled Appointments",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sort by DateTStamp": {
      "main": [
        [
          {
            "node": "Select latest DateTStamp",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AptDateTime to UTC": {
      "main": [
        [
          {
            "node": "AptDateTimeEnd calculate in UTC",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AptDateTimeEnd calculate in UTC": {
      "main": [
        [
          {
            "node": "NOP - Got future OD appointments",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OD Get Patient Info by ID": {
      "main": [
        [
          {
            "node": "Merge Apt and Patient data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Apt and Patient data": {
      "main": [
        [
          {
            "node": "Loop on pagination",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OD API Get New Future Scheduled Appointments": {
      "main": [
        [
          {
            "node": "OD Get Patient Info by ID",
            "type": "main",
            "index": 0
          },
          {
            "node": "Merge Apt and Patient data",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Merge": {
      "main": [
        [
          {
            "node": "New Event found?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Calendar Event cancelled?": {
      "main": [
        [
          {
            "node": "Try undo event cancellation User 1",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Google Calendar Update Event (\"touch\") User 1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "NOP - Got future OD appointments": {
      "main": [
        [
          {
            "node": "NOP - Distribute to Google Calendards",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Select latest DateTStamp": {
      "main": [
        [
          {
            "node": "update lastaccess variable",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "NOP - Distribute to Google Calendards": {
      "main": [
        [
          {
            "node": "Select Providers for User 1",
            "type": "main",
            "index": 0
          },
          {
            "node": "Select Providers for User 2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "New Event found?": {
      "main": [
        [
          {
            "node": "Google Calendar Create Event User 1",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Google Calendar Event cancelled?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge1": {
      "main": [
        [
          {
            "node": "New Event found?1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Calendar Event cancelled?1": {
      "main": [
        [
          {
            "node": "Try undo event cancellation User  2",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Google Calendar Update Event (\"touch\") User  2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "New Event found?1": {
      "main": [
        [
          {
            "node": "Google Calendar Create Event User  2",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Google Calendar Event cancelled?1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Test Data": {
      "main": [
        [
          {
            "node": "Today",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop on pagination": {
      "main": [
        [
          {
            "node": "Merge paginated data",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Update Pagination offset",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge paginated data": {
      "main": [
        [
          {
            "node": "AptDateTime to UTC",
            "type": "main",
            "index": 0
          },
          {
            "node": "Sort by DateTStamp",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Calendar of User 1": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Select Providers for User 1": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          },
          {
            "node": "Google Calendar of User 1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Calendar of User 2": {
      "main": [
        [
          {
            "node": "Merge1",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Select Providers for User 2": {
      "main": [
        [
          {
            "node": "Merge1",
            "type": "main",
            "index": 0
          },
          {
            "node": "Google Calendar of User 2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {}
}

With best regards

Joerg

joergzastrau
Posts: 46
Joined: Sun Feb 27, 2022 2:53 am

Re: Schedules with google - Update 2/2 (using n8n) V2.x

Post by joergzastrau » Thu Aug 01, 2024 7:49 am

2nd Part.

Delete Google Evens (Google Sync Part 2/2) V2.1

Code: Select all

{
  "meta": {
    "instanceId": "c5bbe0e4dd0c2a71ee01ba0477f0fe876e4ef1ddb06022d07739fda528d4f9f1"
  },
  "nodes": [
    {
      "parameters": {},
      "id": "1ba8462e-9a46-4d57-9f5d-64b5915711df",
      "name": "When clicking ‘Test workflow’",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        -1780,
        900
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes"
            }
          ]
        }
      },
      "id": "0bdabae9-aa50-4241-9bc9-56414e3521ac",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        -1780,
        720
      ]
    },
    {
      "parameters": {},
      "id": "412733d9-8ce7-406b-b373-23194e9065ed",
      "name": "Merge2",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3,
      "position": [
        660,
        580
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "9aafa027-7142-4134-bbfe-b843edb47304",
              "name": "lastaccess",
              "value": "2024-08-01 13:30",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "7e9c263e-4717-4ec2-b63e-c5b58072e23b",
      "name": "Test Data",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        -1540,
        900
      ]
    },
    {
      "parameters": {
        "sortFieldsUi": {
          "sortField": [
            {
              "fieldName": "=DateTStamp"
            }
          ]
        },
        "options": {
          "disableDotNotation": false
        }
      },
      "id": "6b79158c-1bd8-4992-a1c6-5c84839acb86",
      "name": "Sort",
      "type": "n8n-nodes-base.sort",
      "typeVersion": 1,
      "position": [
        880,
        580
      ],
      "alwaysOutputData": false,
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "67abea0c-854c-4426-b607-1549c1da9b3d",
              "name": "=lastaccess",
              "value": "={{ $json[\"DateTStamp\"] }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "c62bb310-0c2d-4c53-abe2-c4c112d6a99b",
      "name": "Select latest Update",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1140,
        560
      ],
      "executeOnce": true
    },
    {
      "parameters": {
        "value": "={{$json[\"lastaccess\"]}}",
        "dataPropertyName": "lastaccess",
        "custom": true,
        "options": {
          "toTimezone": "UTC"
        }
      },
      "id": "d4c26dda-ad32-45f2-966a-9fe615bf2229",
      "name": "AptDateTime to UTC",
      "type": "n8n-nodes-base.dateTime",
      "typeVersion": 1,
      "position": [
        -1320,
        720
      ]
    },
    {
      "parameters": {
        "jsCode": "// Get the global workflow static data\nconst workflowStaticData = $getWorkflowStaticData('global');\n\nreturn {json:{\n\t\t\"lastaccess\": workflowStaticData.lastaccess\n  }\n}\n"
      },
      "id": "f837ba79-a5fe-413c-8920-cb0d418b2a59",
      "name": "Restore lastaccess variable",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -1540,
        720
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Get the global workflow static data\nconst workflowStaticData = $getWorkflowStaticData('global');\n\n// Update its data\nworkflowStaticData.lastaccess = $json[\"lastaccess\"];\n\nreturn {json:{\n\t\t\"content\": workflowStaticData\n  }\n}\n"
      },
      "id": "19e47fe8-367f-4cfc-8478-89af725036f0",
      "name": "update lastaccess variable",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1360,
        560
      ]
    },
    {
      "parameters": {
        "jsCode": "// Prepare for OD Database query\n\nfor (var i = 0; i < items.length;) {\n  // Save last access date in every item\n//   items[i].json.DateTStamp =$node[\"Today - 1 month\"].json[\"lastaccess\"];\n\n  // only touch opendent items \n  if (!items[i].json.id.startsWith('opendent')) {\n    items.splice(i,1);\n  } else {\n    // remove opendental-prefix from id\n    items[i].json.GoogleID = parseInt(items[i].json.id.replace('opendent',''));\n    // remove if already updated or invalid opendental-id (NaN)\n//    if ((((items[0].json.DateTStamp)==(items[0].json.updated)))||isNaN(items[i].json.GoogleID)) {\n    if (isNaN(items[i].json.GoogleID)) {\n        items.splice(i,1);\n    }\n    else i++;\n  }\n}\n\nreturn items;"
      },
      "id": "b07f40a5-fbf8-4bad-8c54-c6dd4c541cde",
      "name": "Delete and Trim Apt ID Prefix, filter OD events",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -900,
        720
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "ea128fa8-d1f4-4d9e-820a-e5631897a9e5",
              "leftValue": "={{$json[\"GoogleID\"]}}",
              "rightValue": "={{$json[\"AptNum\"]}}",
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "c2fb4d22-e010-40c5-adfc-b7e1e21cdbd0",
      "name": "Google Event and OD Apt entry",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        -240,
        700
      ]
    },
    {
      "parameters": {
        "mode": "combine",
        "combineBy": "combineByPosition",
        "options": {}
      },
      "id": "dfb83808-7993-4eac-8177-a01066fae8c5",
      "name": "Merge",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3,
      "position": [
        -460,
        700
      ]
    },
    {
      "parameters": {
        "url": "=https://api.opendental.com/api/v1/appointments/{{$json[\"GoogleID\"]}}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {}
      },
      "id": "9528f643-54d0-457e-b6c0-fb42a525cec2",
      "name": "OD Get All Events",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        -680,
        580
      ],
      "alwaysOutputData": true,
      "credentials": {
        "httpHeaderAuth": {
          "id": "c8ZCHq07tFc7C8sw",
          "name": "Header Auth account KL Office"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "b84faa25-33c6-4ce7-a520-aafde07a0175",
              "leftValue": "={{$json[\"status\"]}}",
              "rightValue": "cancelled",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            },
            {
              "id": "b3170cb7-b5cb-49a6-a093-7d5fa707269a",
              "leftValue": "{{$json[\"status\"]}}",
              "rightValue": "Cancelled",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "or"
        },
        "options": {}
      },
      "id": "d66d55d1-22fd-47c0-a887-d14d988f918e",
      "name": "Google Event Cancelled?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        240,
        580
      ]
    },
    {
      "parameters": {
        "method": "PATCH",
        "url": "=https://www.googleapis.com/calendar/v3/calendars/srmgug2008lngoep8d3o49fcmg@group.calendar.google.com/events/{{$json[\"id\"]}}",
        "authentication": "genericCredentialType",
        "genericAuthType": "oAuth2Api",
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "status",
              "value": "confirmed"
            }
          ]
        },
        "options": {}
      },
      "id": "76be3991-30f6-4711-930b-660d1ee0b7e1",
      "name": "Try undelete marked Google events marked cancelled1",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        460,
        440
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "c8ZCHq07tFc7C8sw",
          "name": "Header Auth account KL Office"
        },
        "oAuth2Api": {
          "id": "rNMBNWmZs1mbgJwO",
          "name": "Google Calendar Account Yang"
        }
      }
    },
    {
      "parameters": {
        "operation": "getAll",
        "calendar": {
          "__rl": true,
          "value": "srmgug2008lngoep8d3o49fcmg@group.calendar.google.com",
          "mode": "list",
          "cachedResultName": "KL Office"
        },
        "limit": 300,
        "options": {
          "timeMin": "={{ $json[\"lastaccess\"] }}"
        }
      },
      "id": "fd943d56-6c08-4f4e-8ce3-4286c05ccb84",
      "name": "Google Calendar Get All Events updated",
      "type": "n8n-nodes-base.googleCalendar",
      "typeVersion": 1.1,
      "position": [
        -1120,
        720
      ],
      "credentials": {
        "googleCalendarOAuth2Api": {
          "id": "T5W9zRGj16MupgOn",
          "name": "Google Calendar account Yang"
        }
      }
    },
    {
      "parameters": {
        "operation": "delete",
        "calendar": {
          "__rl": true,
          "value": "srmgug2008lngoep8d3o49fcmg@group.calendar.google.com",
          "mode": "list",
          "cachedResultName": "KL Office"
        },
        "eventId": "={{ $json.id }}",
        "options": {}
      },
      "id": "e7a39c68-23e6-46d4-80f6-a0eb61fe9a72",
      "name": "Google Calendar",
      "type": "n8n-nodes-base.googleCalendar",
      "typeVersion": 1.1,
      "position": [
        240,
        760
      ],
      "credentials": {
        "googleCalendarOAuth2Api": {
          "id": "T5W9zRGj16MupgOn",
          "name": "Google Calendar account Yang"
        }
      },
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict"
                },
                "conditions": [
                  {
                    "leftValue": "={{$json[\"AptStatus\"]}}",
                    "rightValue": "Scheduled",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict"
                },
                "conditions": [
                  {
                    "id": "d06f0786-2dd5-4596-a577-c128b295bf3e",
                    "leftValue": "={{$json[\"AptStatus\"]}}",
                    "rightValue": "Broken",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict"
                },
                "conditions": [
                  {
                    "id": "3e7c0a1f-6269-48b5-901d-d5169e7c1e3d",
                    "leftValue": "={{$json[\"AptStatus\"]}}",
                    "rightValue": "cancelled",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict"
                },
                "conditions": [
                  {
                    "id": "68d01716-a2c1-400c-8777-7a43f61f42c9",
                    "leftValue": "{{$json[\"AptStatus\"]}}",
                    "rightValue": "Scheduled",
                    "operator": {
                      "type": "string",
                      "operation": "notExists",
                      "singleValue": true
                    }
                  }
                ],
                "combinator": "and"
              }
            }
          ]
        },
        "options": {}
      },
      "id": "86c605ad-8d0a-4b37-bbe8-9f5fcbf74c89",
      "name": "OD Apt Scheduled, Broken or cancelled?",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3,
      "position": [
        0,
        620
      ]
    }
  ],
  "connections": {
    "When clicking ‘Test workflow’": {
      "main": [
        [
          {
            "node": "Test Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Restore lastaccess variable",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge2": {
      "main": [
        [
          {
            "node": "Sort",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Test Data": {
      "main": [
        [
          {
            "node": "AptDateTime to UTC",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sort": {
      "main": [
        [
          {
            "node": "Select latest Update",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Select latest Update": {
      "main": [
        [
          {
            "node": "update lastaccess variable",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AptDateTime to UTC": {
      "main": [
        [
          {
            "node": "Google Calendar Get All Events updated",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Restore lastaccess variable": {
      "main": [
        [
          {
            "node": "AptDateTime to UTC",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Delete and Trim Apt ID Prefix, filter OD events": {
      "main": [
        [
          {
            "node": "OD Get All Events",
            "type": "main",
            "index": 0
          },
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Google Event and OD Apt entry": {
      "main": [
        [
          {
            "node": "OD Apt Scheduled, Broken or cancelled?",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Google Calendar",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge": {
      "main": [
        [
          {
            "node": "Google Event and OD Apt entry",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OD Get All Events": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Event Cancelled?": {
      "main": [
        [
          {
            "node": "Try undelete marked Google events marked cancelled1",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Merge2",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Try undelete marked Google events marked cancelled1": {
      "main": [
        [
          {
            "node": "Merge2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Calendar Get All Events updated": {
      "main": [
        [
          {
            "node": "Delete and Trim Apt ID Prefix, filter OD events",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OD Apt Scheduled, Broken or cancelled?": {
      "main": [
        [
          {
            "node": "Google Event Cancelled?",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Google Calendar",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Google Calendar",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Google Calendar",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {}
}
With best regards

Joerg

joergzastrau
Posts: 46
Joined: Sun Feb 27, 2022 2:53 am

Re: Schedules with google 1/2 (add) 20240817 V2.4

Post by joergzastrau » Sat Aug 17, 2024 9:32 am

Dear all,

adding Webhooks (OD API Events for Appointments) to the existing code was easy. The polling version (full sync for future appointments) is still included, specifically an example for one provider. These are needed for initial setup and in case a provider deletes an appointment in google calendar by accident. However, The polling (full sync for future events) is now 12/24 hours instead of 5/10 minutes.

There was another problem in the previous code when the OD server had a timeout. This resulted in the deletion of multiple events in Google Calendar. This is now fixed. Full sync is not shown in the screenshots below (see code / copy&paste to n8n online).

Open Dental Google Calendar Sync ADD Appointments to Google Calendar (Webhook & polling) V2.4

Code: Select all

{
  "meta": {
    "instanceId": "c5bbe0e4dd0c2a71ee01ba0477f0fe876e4ef1ddb06022d07739fda528d4f9f1"
  },
  "nodes": [
    {
      "parameters": {},
      "id": "43922602-6307-4577-ad02-20fbc359afa0",
      "name": "When clicking ‘Test workflow’",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        -2940,
        280
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours",
              "hoursInterval": 24
            }
          ]
        }
      },
      "id": "53e7ff75-4af6-401c-89c4-d9f49ead55ee",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        -2940,
        100
      ]
    },
    {
      "parameters": {
        "jsCode": "// Get the global workflow static data\nconst workflowStaticData = $getWorkflowStaticData('global');\n\nreturn {json:{\n\t\t\"lastaccess\": workflowStaticData.lastaccess,\n        \"offset\": 0\n  }\n}\n"
      },
      "id": "f00085ef-4cc6-4bff-a472-ea6ad6500892",
      "name": "Restore lastaccess variable",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -2740,
        100
      ]
    },
    {
      "parameters": {
        "value": "={{ new Date(new Date().getTime()) }}",
        "dataPropertyName": "DateToday",
        "toFormat": "YYYY-MM-DD",
        "options": {}
      },
      "name": "Today",
      "type": "n8n-nodes-base.dateTime",
      "typeVersion": 1,
      "position": [
        -2540,
        280
      ],
      "id": "68adfc56-a0b0-4808-a7e5-945d55a1b6a7"
    },
    {
      "parameters": {
        "keepOnlySet": true,
        "values": {
          "number": [
            {
              "name": "offset",
              "value": "={{($runIndex+1)*50}}"
            }
          ]
        },
        "options": {}
      },
      "name": "Update Pagination offset",
      "type": "n8n-nodes-base.set",
      "typeVersion": 1,
      "position": [
        -1480,
        440
      ],
      "executeOnce": true,
      "id": "a94f9646-b7e5-4616-8b0f-bd930f827ab6"
    },
    {
      "parameters": {
        "operation": "sort",
        "sortFieldsUi": {
          "sortField": [
            {
              "fieldName": "DateTStamp",
              "order": "descending"
            }
          ]
        },
        "options": {}
      },
      "name": "Sort by DateTStamp",
      "type": "n8n-nodes-base.itemLists",
      "typeVersion": 1,
      "position": [
        -1240,
        60
      ],
      "id": "7287d813-dd44-40de-88aa-c4d907524f96",
      "continueOnFail": true
    },
    {
      "parameters": {
        "value": "={{$json[\"AptDateTime\"]}}",
        "dataPropertyName": "AptStartISOUTC",
        "custom": true,
        "options": {
          "toTimezone": "UTC"
        }
      },
      "id": "1d8c764d-e650-4087-a5b7-dac8f431efe4",
      "name": "AptDateTime to UTC",
      "type": "n8n-nodes-base.dateTime",
      "typeVersion": 1,
      "position": [
        -1240,
        880
      ]
    },
    {
      "parameters": {
        "action": "calculate",
        "value": "={{$json[\"AptStartISOUTC\"]}}",
        "duration": "={{$json[\"Pattern\"].length*5*60}}",
        "timeUnit": "seconds",
        "dataPropertyName": "AptEndISOUTC",
        "options": {
          "fromFormat": ""
        }
      },
      "id": "8e379479-0fe6-4630-8b57-fbada6418bce",
      "name": "AptDateTimeEnd calculate in UTC",
      "type": "n8n-nodes-base.dateTime",
      "typeVersion": 1,
      "position": [
        -1040,
        880
      ]
    },
    {
      "parameters": {
        "authentication": "headerAuth",
        "url": "=http://192.168.1.60:30223/api/v1/patients/{{$json[\"PatNum\"]}}",
        "options": {
          "splitIntoItems": false
        }
      },
      "name": "OD Get Patient Info by ID",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 1,
      "position": [
        -2120,
        100
      ],
      "alwaysOutputData": false,
      "id": "59ab8330-8f51-4de9-9a47-3977edae4116",
      "credentials": {
        "httpHeaderAuth": {
          "id": "c8ZCHq07tFc7C8sw",
          "name": "Header Auth account KL Office"
        }
      },
      "continueOnFail": true
    },
    {
      "parameters": {
        "mode": "combine",
        "combineBy": "combineByPosition",
        "options": {}
      },
      "id": "b4230c70-98ae-4328-95f6-d711d0ff9618",
      "name": "Merge Apt and Patient data",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3,
      "position": [
        -1900,
        260
      ]
    },
    {
      "parameters": {
        "authentication": "headerAuth",
        "url": "=http://192.168.1.60:30223/api/v1/appointments",
        "options": {
          "splitIntoItems": true
        },
        "queryParametersUi": {
          "parameter": [
            {
              "name": "AptStatus",
              "value": "Scheduled"
            },
            {
              "name": "DateTStamp",
              "value": "={{ $json.lastaccess }}"
            },
            {
              "name": "dateStart",
              "value": "={{ $json.DateToday }}"
            },
            {
              "name": "Offset",
              "value": "={{ $json.offset }}"
            },
            {
              "name": "Limit",
              "value": "50"
            }
          ]
        }
      },
      "name": "OD API Get New Future Scheduled Appointments",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 1,
      "position": [
        -2320,
        280
      ],
      "alwaysOutputData": false,
      "notesInFlow": true,
      "id": "3c9c9988-406f-4ccd-870c-48fb46a09059",
      "credentials": {
        "httpHeaderAuth": {
          "id": "c8ZCHq07tFc7C8sw",
          "name": "Header Auth account KL Office"
        }
      },
      "notes": "For production use Date_Stop and DateTStamp in query."
    },
    {
      "parameters": {
        "mode": "combine",
        "combineBy": "combineByPosition",
        "options": {}
      },
      "id": "b33ca917-691d-4b6a-b920-7b159b0671df",
      "name": "Merge",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3,
      "position": [
        -2300,
        1320
      ]
    },
    {
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{$json[\"status\"]}}",
              "value2": "cancelled"
            }
          ]
        }
      },
      "name": "Google Calendar Event cancelled?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        -1900,
        1440
      ],
      "id": "433aae50-c09f-4305-a11e-2b87bacfb22f"
    },
    {
      "parameters": {},
      "id": "55057259-0851-47c6-a5aa-bfe8e69cbdc2",
      "name": "NOP - Got future OD appointments",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        -840,
        1020
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Get the global workflow static data\nconst workflowStaticData = $getWorkflowStaticData('global');\n\n// Update its data\nworkflowStaticData.lastaccess = $json[\"lastaccess\"];\n\nreturn {json:{\n\t\t\"content\": workflowStaticData\n  }\n}\n"
      },
      "id": "fbc13e57-0409-461c-a254-4cc70d8794c3",
      "name": "update lastaccess variable",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -880,
        60
      ]
    },
    {
      "parameters": {
        "keepOnlySet": true,
        "values": {
          "string": [
            {
              "name": "lastaccess",
              "value": "={{$json[\"DateTStamp\"]}}"
            }
          ]
        },
        "options": {
          "dotNotation": true
        }
      },
      "name": "Select latest DateTStamp",
      "type": "n8n-nodes-base.set",
      "typeVersion": 1,
      "position": [
        -1060,
        60
      ],
      "executeOnce": true,
      "id": "dd5f46b7-b83a-4501-a67a-fd7b26d379ea"
    },
    {
      "parameters": {},
      "id": "ccf3f3b9-b021-41de-9a4a-19bd8fe40dbf",
      "name": "NOP - Distribute to Google Calendards",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        -2940,
        1320
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "c6e036ce-e611-4652-aa30-be041fd43a66",
              "leftValue": "={{ $json.id }}",
              "rightValue": "",
              "operator": {
                "type": "string",
                "operation": "empty",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "7e15c579-cfce-4455-b004-b5c85bc909e4",
      "name": "New Event found?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        -2100,
        1320
      ]
    },
    {
      "parameters": {
        "mode": "combine",
        "combineBy": "combineByPosition",
        "options": {}
      },
      "id": "010f62f2-e98a-493f-8511-7481b36e67ad",
      "name": "Merge1",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3,
      "position": [
        -2300,
        1900
      ]
    },
    {
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{$json[\"status\"]}}",
              "value2": "cancelled"
            }
          ]
        }
      },
      "name": "Google Calendar Event cancelled?1",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        -1900,
        2020
      ],
      "id": "efeaab2d-7f03-4885-ad84-04286ef6b24d"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "c6e036ce-e611-4652-aa30-be041fd43a66",
              "leftValue": "={{ $json.id }}",
              "rightValue": "",
              "operator": {
                "type": "string",
                "operation": "empty",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "79d844ec-4d5b-4fd7-9974-b0b21e5bd0f8",
      "name": "New Event found?1",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        -2100,
        1900
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "9aafa027-7142-4134-bbfe-b843edb47304",
              "name": "lastaccess",
              "value": "2024-03-01 02:45:00",
              "type": "string"
            },
            {
              "id": "2d39cdfa-f99d-4f09-a83c-a78572affac0",
              "name": "offset",
              "value": 0,
              "type": "number"
            }
          ]
        },
        "options": {}
      },
      "id": "4252a532-dcdf-44b7-95b0-cb4d3388f2a9",
      "name": "Test Data",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        -2740,
        280
      ]
    },
    {
      "parameters": {
        "conditions": {
          "number": [
            {
              "value1": "={{$items().length}}",
              "value2": 50
            }
          ]
        }
      },
      "name": "Loop on pagination",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        -1680,
        260
      ],
      "id": "b4164f32-c5b2-4558-a51a-edee6c8b1ece"
    },
    {
      "parameters": {
        "jsCode": "const allData = []\n\nlet counter = 0;\ndo {\n  try {\n    const aja = $items(\"Merge Apt and Patient data\",0,counter);                   \n    allData.push.apply(allData, aja);\n  } catch (error) {\n    return allData;  \n  }\n  counter++;\n} while(true);\n"
      },
      "id": "134a7a2f-23fe-4fa0-b304-9847d65cef2e",
      "name": "Merge paginated data",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -1480,
        260
      ]
    },
    {
      "parameters": {
        "operation": "update",
        "calendar": {
          "__rl": true,
          "value": "zastrau@gmail.com",
          "mode": "list",
          "cachedResultName": "zastrau@gmail.com"
        },
        "eventId": "={{$json[\"id\"]}}",
        "updateFields": {
          "description": "=Procedure: {{$json[\"ProcDescript\"]}}",
          "end": "={{$json[\"AptEndISOUTC\"]}}",
          "start": "={{$json[\"AptStartISOUTC\"]}}",
          "summary": "={{$json[\"FName\"]}} {{$json[\"LName\"].substring(0,1)}}. {{$json[\"Note\"].substring(0,700)}}"
        }
      },
      "name": "Google Calendar Update Event (\"touch\") User  2",
      "type": "n8n-nodes-base.googleCalendar",
      "typeVersion": 1,
      "position": [
        -1480,
        2220
      ],
      "retryOnFail": true,
      "waitBetweenTries": 5000,
      "id": "c8615476-fea7-47f5-ac4f-587a6d27d5a1",
      "credentials": {
        "googleCalendarOAuth2Api": {
          "id": "a12lZKdLn26CvrR2",
          "name": "Google Calendar account Zastrau"
        }
      },
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "calendar": {
          "__rl": true,
          "value": "=zastrau@gmail.com",
          "mode": "id",
          "__regex": "(^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*)"
        },
        "start": "={{$json[\"AptStartISOUTC\"]}}",
        "end": "={{$json[\"AptEndISOUTC\"]}}",
        "additionalFields": {
          "attendees": [],
          "description": "=Procedure: {{$json[\"ProcDescript\"]}}",
          "id": "=opendent{{ $json[\"AptNum\"] }}",
          "summary": "={{$json[\"FName\"]}} {{$json[\"LName\"].substring(0,1)}}. {{$json[\"Note\"].substring(0,700)}}"
        }
      },
      "name": "Google Calendar Create Event User  2",
      "type": "n8n-nodes-base.googleCalendar",
      "typeVersion": 1,
      "position": [
        -1680,
        1820
      ],
      "retryOnFail": true,
      "waitBetweenTries": 5000,
      "id": "e47d1bbb-a426-424b-9df8-23dd2c5f6396",
      "credentials": {
        "googleCalendarOAuth2Api": {
          "id": "a12lZKdLn26CvrR2",
          "name": "Google Calendar account Zastrau"
        }
      },
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "operation": "update",
        "calendar": {
          "__rl": true,
          "value": "srmgug2008lngoep8d3o49fcmg@group.calendar.google.com",
          "mode": "list",
          "cachedResultName": "KL Office"
        },
        "eventId": "={{$json[\"id\"]}}",
        "updateFields": {
          "description": "=Procedure: {{$json[\"ProcDescript\"]}}",
          "end": "={{$json[\"AptEndISOUTC\"]}}",
          "start": "={{$json[\"AptStartISOUTC\"]}}",
          "summary": "={{$json[\"FName\"]}} {{$json[\"LName\"].substring(0,1)}}. {{$json[\"Note\"].substring(0,700)}}"
        }
      },
      "name": "Google Calendar Update Event (\"touch\") User 1",
      "type": "n8n-nodes-base.googleCalendar",
      "typeVersion": 1,
      "position": [
        -1480,
        1620
      ],
      "retryOnFail": true,
      "waitBetweenTries": 5000,
      "id": "3e46a54b-00bb-42e2-9e0c-67230156a66f",
      "credentials": {
        "googleCalendarOAuth2Api": {
          "id": "T5W9zRGj16MupgOn",
          "name": "Google Calendar account Yang"
        }
      },
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "calendar": {
          "__rl": true,
          "value": "srmgug2008lngoep8d3o49fcmg@group.calendar.google.com",
          "mode": "list",
          "cachedResultName": "KL Office"
        },
        "start": "={{$json[\"AptStartISOUTC\"]}}",
        "end": "={{$json[\"AptEndISOUTC\"]}}",
        "additionalFields": {
          "attendees": [],
          "description": "=Procedure: {{$json[\"ProcDescript\"]}}",
          "id": "=opendent{{ $json[\"AptNum\"] }}",
          "summary": "={{$json[\"FName\"]}} {{$json[\"LName\"].substring(0,1)}}. {{$json[\"Note\"].substring(0,700)}}"
        }
      },
      "name": "Google Calendar Create Event User 1",
      "type": "n8n-nodes-base.googleCalendar",
      "typeVersion": 1,
      "position": [
        -1680,
        1240
      ],
      "retryOnFail": true,
      "waitBetweenTries": 5000,
      "id": "8430e48c-d0fa-4f2b-8d40-702a88cb7819",
      "credentials": {
        "googleCalendarOAuth2Api": {
          "id": "T5W9zRGj16MupgOn",
          "name": "Google Calendar account Yang"
        }
      },
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "operation": "get",
        "calendar": {
          "__rl": true,
          "value": "srmgug2008lngoep8d3o49fcmg@group.calendar.google.com",
          "mode": "list",
          "cachedResultName": "KL Office"
        },
        "eventId": "=opendent{{ $json[\"AptNum\"] }}",
        "options": {}
      },
      "id": "a701cf3e-1b24-4323-bac5-edad312a2627",
      "name": "Google Calendar of User 1",
      "type": "n8n-nodes-base.googleCalendar",
      "typeVersion": 1.1,
      "position": [
        -2520,
        1440
      ],
      "alwaysOutputData": true,
      "credentials": {
        "googleCalendarOAuth2Api": {
          "id": "T5W9zRGj16MupgOn",
          "name": "Google Calendar account Yang"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "conditions": {
          "number": [
            {
              "value1": "={{$json[\"ProvNum\"]}}",
              "operation": "equal",
              "value2": 1
            }
          ]
        }
      },
      "name": "Select Providers for User 1",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        -2740,
        1320
      ],
      "id": "d9255fcf-44aa-454e-8f6b-e0164d9c0cd4"
    },
    {
      "parameters": {
        "operation": "get",
        "calendar": {
          "__rl": true,
          "value": "zastrau@gmail.com",
          "mode": "list",
          "cachedResultName": "zastrau@gmail.com"
        },
        "eventId": "=opendent{{ $json[\"AptNum\"] }}",
        "options": {}
      },
      "id": "62041de0-ffea-4193-a401-fcc41ed3af30",
      "name": "Google Calendar of User 2",
      "type": "n8n-nodes-base.googleCalendar",
      "typeVersion": 1.1,
      "position": [
        -2520,
        2020
      ],
      "alwaysOutputData": true,
      "credentials": {
        "googleCalendarOAuth2Api": {
          "id": "a12lZKdLn26CvrR2",
          "name": "Google Calendar account Zastrau"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "conditions": {
          "number": [
            {
              "value1": "={{$json[\"ProvNum\"]}}",
              "operation": "equal",
              "value2": 1
            }
          ]
        }
      },
      "name": "Select Providers for User 2",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        -2740,
        1900
      ],
      "id": "7d41e8ac-a510-4900-a5e2-753b0baabafb"
    },
    {
      "parameters": {
        "method": "PATCH",
        "url": "=https://www.googleapis.com/calendar/v3/calendars/zastrau%40gmail.com/events/{{$json[\"id\"]}}",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "googleCalendarOAuth2Api",
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "status",
              "value": "confirmed"
            }
          ]
        },
        "options": {}
      },
      "id": "43a6350e-2033-4086-80ab-49ace6f897ef",
      "name": "Try undo event cancellation User 2",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        -1680,
        2000
      ],
      "alwaysOutputData": true,
      "credentials": {
        "oAuth2Api": {
          "id": "f4pZB4Q3vvy7gzwF",
          "name": "Google Calendar Joerg Zastrau"
        },
        "googleCalendarOAuth2Api": {
          "id": "a12lZKdLn26CvrR2",
          "name": "Google Calendar account Zastrau"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {},
      "id": "f919163f-e8a2-45ae-b5c3-c84b47a3a3bd",
      "name": "Merge2",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3,
      "position": [
        -1680,
        2220
      ]
    },
    {
      "parameters": {},
      "id": "a28649f8-f4c4-44c5-80e0-3e746248a9cc",
      "name": "Merge3",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3,
      "position": [
        -1680,
        1620
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes",
              "minutesInterval": 30
            }
          ]
        }
      },
      "id": "263fb0b0-9655-4657-ad71-0738df16f859",
      "name": "Schedule Trigger1",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        -2940,
        840
      ]
    },
    {
      "parameters": {
        "method": "DELETE",
        "url": "http://192.168.1.60:30223/api/v1/subscriptions/32",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {}
      },
      "id": "353024af-4084-4b6e-947e-6f22b0c7a16f",
      "name": "DEBUG Delete subscription",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        -2740,
        660
      ],
      "alwaysOutputData": true,
      "credentials": {
        "httpHeaderAuth": {
          "id": "c8ZCHq07tFc7C8sw",
          "name": "Header Auth account KL Office"
        }
      },
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "url": "http://192.168.1.60:30223/api/v1/subscriptions",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {}
      },
      "id": "d69efc91-4e3b-498a-a630-9ef19b4878ab",
      "name": "DEBUG Get subscriptions",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        -2940,
        660
      ],
      "alwaysOutputData": true,
      "credentials": {
        "httpHeaderAuth": {
          "id": "c8ZCHq07tFc7C8sw",
          "name": "Header Auth account KL Office"
        }
      }
    },
    {
      "parameters": {
        "fieldToSplitOut": "body",
        "options": {}
      },
      "id": "60dbc358-8593-4a8c-a71b-b580d941bbf8",
      "name": "Split Out",
      "type": "n8n-nodes-base.splitOut",
      "typeVersion": 1,
      "position": [
        -2740,
        1020
      ]
    },
    {
      "parameters": {
        "authentication": "headerAuth",
        "url": "=http://192.168.1.60:30223/api/v1/patients/{{$json[\"PatNum\"]}}",
        "options": {
          "splitIntoItems": false
        }
      },
      "name": "OD Get Patient Info by ID1",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 1,
      "position": [
        -2100,
        920
      ],
      "alwaysOutputData": false,
      "id": "56a1e6ac-dbcf-4953-9707-137aa136365d",
      "credentials": {
        "httpHeaderAuth": {
          "id": "c8ZCHq07tFc7C8sw",
          "name": "Header Auth account KL Office"
        }
      },
      "continueOnFail": true
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "appointment",
        "options": {
          "rawBody": false
        }
      },
      "id": "d2bad7b9-11b0-4148-9abf-bed9b8ade510",
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        -2940,
        1020
      ],
      "webhookId": "53b9b8df-81ae-4935-bcf2-78539809826d"
    },
    {
      "parameters": {
        "mode": "combine",
        "combineBy": "combineByPosition",
        "options": {}
      },
      "id": "ce744bd3-a2e3-4f1f-8c14-8a1bd393c4be",
      "name": "Merge Apt and Patient data1",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3,
      "position": [
        -1480,
        1020
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "http://192.168.1.60:30223/api/v1/subscriptions",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "{\n\"EndPointUrl\": \"http://192.168.1.60:5679/webhook/appointment\",\n\"Workstation\": \"OFFICESERVER\",\n\"WatchTable\": \"Appointment\",\n\"PollingSeconds\": 2\n}",
        "options": {}
      },
      "id": "0d2c352f-8274-46a1-8ab1-610f626e7530",
      "name": "Subscription Add App Production",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        -2740,
        840
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "c8ZCHq07tFc7C8sw",
          "name": "Header Auth account KL Office"
        }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "http://192.168.1.60:30223/api/v1/subscriptions",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "{\n\"EndPointUrl\": \"http://192.168.1.60:5679/webhook-test/appointment\",\n\"Workstation\": \"OFFICESERVER\",\n\"WatchTable\": \"Appointment\",\n\"PollingSeconds\": 2\n}",
        "options": {}
      },
      "id": "d18a8470-f7cc-4d5b-8f40-b741ea3b5075",
      "name": "Subscription Add App Test",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        -2540,
        840
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "c8ZCHq07tFc7C8sw",
          "name": "Header Auth account KL Office"
        }
      },
      "disabled": true
    }
  ],
  "connections": {
    "When clicking ‘Test workflow’": {
      "main": [
        [
          {
            "node": "Test Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Restore lastaccess variable",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Restore lastaccess variable": {
      "main": [
        [
          {
            "node": "Today",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Today": {
      "main": [
        [
          {
            "node": "OD API Get New Future Scheduled Appointments",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Pagination offset": {
      "main": [
        [
          {
            "node": "OD API Get New Future Scheduled Appointments",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sort by DateTStamp": {
      "main": [
        [
          {
            "node": "Select latest DateTStamp",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AptDateTime to UTC": {
      "main": [
        [
          {
            "node": "AptDateTimeEnd calculate in UTC",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AptDateTimeEnd calculate in UTC": {
      "main": [
        [
          {
            "node": "NOP - Got future OD appointments",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OD Get Patient Info by ID": {
      "main": [
        [
          {
            "node": "Merge Apt and Patient data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Apt and Patient data": {
      "main": [
        [
          {
            "node": "Loop on pagination",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OD API Get New Future Scheduled Appointments": {
      "main": [
        [
          {
            "node": "OD Get Patient Info by ID",
            "type": "main",
            "index": 0
          },
          {
            "node": "Merge Apt and Patient data",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Merge": {
      "main": [
        [
          {
            "node": "New Event found?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Calendar Event cancelled?": {
      "main": [
        [
          {
            "node": "Merge3",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Merge3",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "NOP - Got future OD appointments": {
      "main": [
        [
          {
            "node": "NOP - Distribute to Google Calendards",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Select latest DateTStamp": {
      "main": [
        [
          {
            "node": "update lastaccess variable",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "NOP - Distribute to Google Calendards": {
      "main": [
        [
          {
            "node": "Select Providers for User 1",
            "type": "main",
            "index": 0
          },
          {
            "node": "Select Providers for User 2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "New Event found?": {
      "main": [
        [
          {
            "node": "Google Calendar Create Event User 1",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Google Calendar Event cancelled?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge1": {
      "main": [
        [
          {
            "node": "New Event found?1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Calendar Event cancelled?1": {
      "main": [
        [
          {
            "node": "Try undo event cancellation User 2",
            "type": "main",
            "index": 0
          },
          {
            "node": "Merge2",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Merge2",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "New Event found?1": {
      "main": [
        [
          {
            "node": "Google Calendar Create Event User  2",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Google Calendar Event cancelled?1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Test Data": {
      "main": [
        [
          {
            "node": "Today",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop on pagination": {
      "main": [
        [
          {
            "node": "Merge paginated data",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Update Pagination offset",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge paginated data": {
      "main": [
        [
          {
            "node": "AptDateTime to UTC",
            "type": "main",
            "index": 0
          },
          {
            "node": "Sort by DateTStamp",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Calendar of User 1": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Select Providers for User 1": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          },
          {
            "node": "Google Calendar of User 1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Calendar of User 2": {
      "main": [
        [
          {
            "node": "Merge1",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Select Providers for User 2": {
      "main": [
        [
          {
            "node": "Merge1",
            "type": "main",
            "index": 0
          },
          {
            "node": "Google Calendar of User 2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge2": {
      "main": [
        [
          {
            "node": "Google Calendar Update Event (\"touch\") User  2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge3": {
      "main": [
        [
          {
            "node": "Google Calendar Update Event (\"touch\") User 1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger1": {
      "main": [
        [
          {
            "node": "Subscription Add App Production",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Out": {
      "main": [
        [
          {
            "node": "OD Get Patient Info by ID1",
            "type": "main",
            "index": 0
          },
          {
            "node": "Merge Apt and Patient data1",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "OD Get Patient Info by ID1": {
      "main": [
        [
          {
            "node": "Merge Apt and Patient data1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook": {
      "main": [
        [
          {
            "node": "Split Out",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Apt and Patient data1": {
      "main": [
        [
          {
            "node": "AptDateTime to UTC",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Subscription Add App Production": {
      "main": [
        [
          {
            "node": "Subscription Add App Test",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {}
}
Part 1/2 - ADD

With best regards

Joerg
Attachments
This is what you might see on the cell. We are a part time office.
This is what you might see on the cell. We are a part time office.
forum-result.png (274.67 KiB) Viewed 13164 times
Adding Google Events with Webhook and Polling V2.4
Adding Google Events with Webhook and Polling V2.4
Forum-20240817-Google_Add.png (94.91 KiB) Viewed 13196 times
Deleting Google Events with Webhook V2.2.1
Deleting Google Events with Webhook V2.2.1
Forum-20240817-Google_Delete.png (73.91 KiB) Viewed 13196 times
Last edited by joergzastrau on Sat Aug 17, 2024 11:56 am, edited 10 times in total.

joergzastrau
Posts: 46
Joined: Sun Feb 27, 2022 2:53 am

Re: Schedules with google 2/2 (delete) 20240817 V2.2

Post by joergzastrau » Sat Aug 17, 2024 9:36 am

This is the code for deletion of Google appointments with n8n. One with webhook and one example for polling (full sync).

OD Google Calendar Sync DELETE Appointments Webhook (V2.2)

Code: Select all

{
  "meta": {
    "instanceId": "c5bbe0e4dd0c2a71ee01ba0477f0fe876e4ef1ddb06022d07739fda528d4f9f1"
  },
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes",
              "minutesInterval": 30
            }
          ]
        }
      },
      "id": "6f4e3e23-62aa-48d6-870d-8ede394506d2",
      "name": "Schedule Trigger1",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        240,
        440
      ]
    },
    {
      "parameters": {
        "method": "DELETE",
        "url": "http://192.168.1.60:30223/api/v1/subscriptions/37",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {}
      },
      "id": "ae5339da-4927-470a-a40b-a5181ef7a36e",
      "name": "DEBUG Delete subscription",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        440,
        260
      ],
      "alwaysOutputData": true,
      "credentials": {
        "httpHeaderAuth": {
          "id": "c8ZCHq07tFc7C8sw",
          "name": "Header Auth account KL Office"
        }
      },
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "url": "http://192.168.1.60:30223/api/v1/subscriptions",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {}
      },
      "id": "d6d97cdf-3f37-4960-9a74-3dffee10d8e8",
      "name": "DEBUG Get subscriptions",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        240,
        260
      ],
      "alwaysOutputData": true,
      "credentials": {
        "httpHeaderAuth": {
          "id": "c8ZCHq07tFc7C8sw",
          "name": "Header Auth account KL Office"
        }
      }
    },
    {
      "parameters": {
        "fieldToSplitOut": "body",
        "options": {}
      },
      "id": "8f0d9085-9096-46cd-9b5f-fa27383600c6",
      "name": "Split Out",
      "type": "n8n-nodes-base.splitOut",
      "typeVersion": 1,
      "position": [
        440,
        620
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "http://192.168.1.60:30223/api/v1/subscriptions",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "{\n\"EndPointUrl\": \"http://192.168.1.60:5679/webhook/appointmentdelete\",\n\"Workstation\": \"OFFICESERVER\",\n\"WatchTable\": \"AppointmentDeleted\",\n\"PollingSeconds\": 2\n}",
        "options": {}
      },
      "id": "64035ec1-0d47-4841-8d90-8cd0c0687612",
      "name": "Subscription Add App Production",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        440,
        440
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "c8ZCHq07tFc7C8sw",
          "name": "Header Auth account KL Office"
        }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "http://192.168.1.60:30223/api/v1/subscriptions",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "{\n\"EndPointUrl\": \"http://192.168.1.60:5679/webhook-test/appointmentdelete\",\n\"Workstation\": \"OFFICESERVER\",\n\"WatchTable\": \"AppointmentDeleted\",\n\"PollingSeconds\": 2\n}",
        "options": {}
      },
      "id": "f5fe6eab-7ff3-4fa7-9609-40ec614a1ef5",
      "name": "Subscription Add App Test",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        640,
        440
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "c8ZCHq07tFc7C8sw",
          "name": "Header Auth account KL Office"
        }
      },
      "disabled": true
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "appointmentdelete",
        "options": {
          "rawBody": false
        }
      },
      "id": "cead47f4-0382-43ff-b673-309aa3dc10e7",
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        240,
        620
      ],
      "webhookId": "53b9b8df-81ae-4935-bcf2-78539809826d"
    },
    {
      "parameters": {
        "conditions": {
          "number": [
            {
              "value1": "={{$json[\"ProvNum\"]}}",
              "operation": "equal",
              "value2": 1
            }
          ]
        }
      },
      "name": "Select Providers for User 2",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        640,
        820
      ],
      "id": "9cb95929-4e81-4b88-aa66-a92d071d23c2"
    },
    {
      "parameters": {
        "conditions": {
          "number": [
            {
              "value1": "={{$json[\"ProvNum\"]}}",
              "operation": "equal",
              "value2": 1
            }
          ]
        }
      },
      "name": "Select Providers for User 1",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        640,
        620
      ],
      "id": "495e42a7-ec93-4369-b69a-2af20a8ca287"
    },
    {
      "parameters": {
        "operation": "delete",
        "calendar": {
          "__rl": true,
          "value": "srmgug2008lngoep8d3o49fcmg@group.calendar.google.com",
          "mode": "list",
          "cachedResultName": "KL Office"
        },
        "eventId": "=opendent{{ $json[\"AptNum\"] }}",
        "options": {}
      },
      "id": "54a59093-b313-478d-8520-3c8dfb68af2c",
      "name": "Google Calendar User 1",
      "type": "n8n-nodes-base.googleCalendar",
      "typeVersion": 1.1,
      "position": [
        860,
        600
      ],
      "credentials": {
        "googleCalendarOAuth2Api": {
          "id": "T5W9zRGj16MupgOn",
          "name": "Google Calendar account Yang"
        }
      },
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "operation": "delete",
        "calendar": {
          "__rl": true,
          "value": "zastrau@gmail.com",
          "mode": "list",
          "cachedResultName": "zastrau@gmail.com"
        },
        "eventId": "=opendent{{ $json[\"AptNum\"] }}",
        "options": {}
      },
      "id": "138dab1f-f248-4d5c-bad6-afb91ad04dcd",
      "name": "Google Calendar User 2",
      "type": "n8n-nodes-base.googleCalendar",
      "typeVersion": 1.1,
      "position": [
        860,
        800
      ],
      "credentials": {
        "googleCalendarOAuth2Api": {
          "id": "a12lZKdLn26CvrR2",
          "name": "Google Calendar account Zastrau"
        }
      },
      "onError": "continueErrorOutput"
    }
  ],
  "connections": {
    "Schedule Trigger1": {
      "main": [
        [
          {
            "node": "Subscription Add App Production",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Out": {
      "main": [
        [
          {
            "node": "Select Providers for User 2",
            "type": "main",
            "index": 0
          },
          {
            "node": "Select Providers for User 1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Subscription Add App Production": {
      "main": [
        [
          {
            "node": "Subscription Add App Test",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook": {
      "main": [
        [
          {
            "node": "Split Out",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Select Providers for User 2": {
      "main": [
        [
          {
            "node": "Google Calendar User 2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Select Providers for User 1": {
      "main": [
        [
          {
            "node": "Google Calendar User 1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {}
}
Open Dental Google Calendar Sync DELETE Appointments (polling) V2.1.1 User 1

Code: Select all

{
  "meta": {
    "instanceId": "c5bbe0e4dd0c2a71ee01ba0477f0fe876e4ef1ddb06022d07739fda528d4f9f1"
  },
  "nodes": [
    {
      "parameters": {},
      "id": "1ba8462e-9a46-4d57-9f5d-64b5915711df",
      "name": "When clicking ‘Test workflow’",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        -1780,
        900
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours",
              "hoursInterval": 12
            }
          ]
        }
      },
      "id": "0bdabae9-aa50-4241-9bc9-56414e3521ac",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        -1780,
        720
      ]
    },
    {
      "parameters": {},
      "id": "412733d9-8ce7-406b-b373-23194e9065ed",
      "name": "Merge2",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3,
      "position": [
        420,
        600
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "9aafa027-7142-4134-bbfe-b843edb47304",
              "name": "lastaccess",
              "value": "2024-08-01 13:30",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "7e9c263e-4717-4ec2-b63e-c5b58072e23b",
      "name": "Test Data",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        -1540,
        900
      ]
    },
    {
      "parameters": {
        "sortFieldsUi": {
          "sortField": [
            {
              "fieldName": "=DateTStamp"
            }
          ]
        },
        "options": {
          "disableDotNotation": false
        }
      },
      "id": "6b79158c-1bd8-4992-a1c6-5c84839acb86",
      "name": "Sort",
      "type": "n8n-nodes-base.sort",
      "typeVersion": 1,
      "position": [
        640,
        600
      ],
      "alwaysOutputData": false,
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "67abea0c-854c-4426-b607-1549c1da9b3d",
              "name": "=lastaccess",
              "value": "={{ $json[\"DateTStamp\"] }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "c62bb310-0c2d-4c53-abe2-c4c112d6a99b",
      "name": "Select latest Update",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        900,
        580
      ],
      "executeOnce": true
    },
    {
      "parameters": {
        "value": "={{$json[\"lastaccess\"]}}",
        "dataPropertyName": "lastaccess",
        "custom": true,
        "options": {
          "toTimezone": "UTC"
        }
      },
      "id": "d4c26dda-ad32-45f2-966a-9fe615bf2229",
      "name": "AptDateTime to UTC",
      "type": "n8n-nodes-base.dateTime",
      "typeVersion": 1,
      "position": [
        -1320,
        720
      ]
    },
    {
      "parameters": {
        "jsCode": "// Get the global workflow static data\nconst workflowStaticData = $getWorkflowStaticData('global');\n\nreturn {json:{\n\t\t\"lastaccess\": workflowStaticData.lastaccess\n  }\n}\n"
      },
      "id": "f837ba79-a5fe-413c-8920-cb0d418b2a59",
      "name": "Restore lastaccess variable",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -1540,
        720
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Get the global workflow static data\nconst workflowStaticData = $getWorkflowStaticData('global');\n\n// Update its data\nworkflowStaticData.lastaccess = $json[\"lastaccess\"];\n\nreturn {json:{\n\t\t\"content\": workflowStaticData\n  }\n}\n"
      },
      "id": "19e47fe8-367f-4cfc-8478-89af725036f0",
      "name": "update lastaccess variable",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1120,
        580
      ]
    },
    {
      "parameters": {
        "jsCode": "// Prepare for OD Database query\n\nfor (var i = 0; i < items.length;) {\n  // Save last access date in every item\n//   items[i].json.DateTStamp =$node[\"Today - 1 month\"].json[\"lastaccess\"];\n\n  // only touch opendent items \n  if (!items[i].json.id.startsWith('opendent')) {\n    items.splice(i,1);\n  } else {\n    // remove opendental-prefix from id\n    items[i].json.GoogleID = parseInt(items[i].json.id.replace('opendent',''));\n    // remove if already updated or invalid opendental-id (NaN)\n//    if ((((items[0].json.DateTStamp)==(items[0].json.updated)))||isNaN(items[i].json.GoogleID)) {\n    if (isNaN(items[i].json.GoogleID)) {\n        items.splice(i,1);\n    }\n    else i++;\n  }\n}\n\nreturn items;"
      },
      "id": "b07f40a5-fbf8-4bad-8c54-c6dd4c541cde",
      "name": "Delete and Trim Apt ID Prefix, filter OD events",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -900,
        720
      ]
    },
    {
      "parameters": {
        "mode": "combine",
        "combineBy": "combineByPosition",
        "options": {}
      },
      "id": "dfb83808-7993-4eac-8177-a01066fae8c5",
      "name": "Merge",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3,
      "position": [
        -460,
        700
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "b84faa25-33c6-4ce7-a520-aafde07a0175",
              "leftValue": "={{$json[\"status\"]}}",
              "rightValue": "cancelled",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            },
            {
              "id": "b3170cb7-b5cb-49a6-a093-7d5fa707269a",
              "leftValue": "{{$json[\"status\"]}}",
              "rightValue": "Cancelled",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "or"
        },
        "options": {}
      },
      "id": "d66d55d1-22fd-47c0-a887-d14d988f918e",
      "name": "Google Event Cancelled?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        0,
        600
      ]
    },
    {
      "parameters": {
        "method": "PATCH",
        "url": "=https://www.googleapis.com/calendar/v3/calendars/srmgug2008lngoep8d3o49fcmg@group.calendar.google.com/events/{{$json[\"id\"]}}",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "googleCalendarOAuth2Api",
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "status",
              "value": "confirmed"
            }
          ]
        },
        "options": {}
      },
      "id": "76be3991-30f6-4711-930b-660d1ee0b7e1",
      "name": "Try undelete marked Google events marked cancelled1",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        220,
        460
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "c8ZCHq07tFc7C8sw",
          "name": "Header Auth account KL Office"
        },
        "oAuth2Api": {
          "id": "rNMBNWmZs1mbgJwO",
          "name": "Google Calendar Account Yang"
        },
        "googleCalendarOAuth2Api": {
          "id": "T5W9zRGj16MupgOn",
          "name": "Google Calendar account Yang"
        }
      }
    },
    {
      "parameters": {
        "operation": "delete",
        "calendar": {
          "__rl": true,
          "value": "srmgug2008lngoep8d3o49fcmg@group.calendar.google.com",
          "mode": "list",
          "cachedResultName": "KL Office"
        },
        "eventId": "={{ $json.id }}",
        "options": {}
      },
      "id": "e7a39c68-23e6-46d4-80f6-a0eb61fe9a72",
      "name": "Google Calendar",
      "type": "n8n-nodes-base.googleCalendar",
      "typeVersion": 1.1,
      "position": [
        0,
        780
      ],
      "credentials": {
        "googleCalendarOAuth2Api": {
          "id": "T5W9zRGj16MupgOn",
          "name": "Google Calendar account Yang"
        }
      },
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict"
                },
                "conditions": [
                  {
                    "leftValue": "={{$json[\"AptStatus\"]}}",
                    "rightValue": "Scheduled",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "Scheduled"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict"
                },
                "conditions": [
                  {
                    "id": "d06f0786-2dd5-4596-a577-c128b295bf3e",
                    "leftValue": "={{$json[\"AptStatus\"]}}",
                    "rightValue": "Complete",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "Complete"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict"
                },
                "conditions": [
                  {
                    "id": "99b6849a-72bd-47c8-946c-85364a21f5ef",
                    "leftValue": "={{$json[\"AptStatus\"]}}",
                    "rightValue": "cancelled",
                    "operator": {
                      "type": "string",
                      "operation": "equals",
                      "name": "filter.operator.equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "cancelled"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict"
                },
                "conditions": [
                  {
                    "id": "35a441ab-021f-482a-bd95-3f205bdab411",
                    "leftValue": "={{$json[\"AptStatus\"]}}",
                    "rightValue": "Broken",
                    "operator": {
                      "type": "string",
                      "operation": "equals",
                      "name": "filter.operator.equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "Broken"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict"
                },
                "conditions": [
                  {
                    "id": "a996a7e5-7944-458a-9cb2-f93310e04768",
                    "leftValue": "={{ $json[\"error\"][\"code\"] }}",
                    "rightValue": "ERR_BAD_REQUEST",
                    "operator": {
                      "type": "string",
                      "operation": "equals",
                      "name": "filter.operator.equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "not found"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict"
                },
                "conditions": [
                  {
                    "id": "c9783a65-b604-42b7-8abd-ce3c172cf064",
                    "leftValue": "={{ $json[\"error\"][\"code\"] }}",
                    "rightValue": "ERR_BAD_RESPONSE",
                    "operator": {
                      "type": "string",
                      "operation": "equals",
                      "name": "filter.operator.equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "ERROR"
            }
          ]
        },
        "options": {
          "fallbackOutput": "extra"
        }
      },
      "id": "2b6caff4-c119-4ed3-a553-85faca939fc8",
      "name": "OD Apt Status?",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3,
      "position": [
        -240,
        660
      ]
    },
    {
      "parameters": {},
      "id": "3c56c74f-b8a1-458a-ab02-2bd522d35503",
      "name": "No Operation, do nothing",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        0,
        980
      ]
    },
    {
      "parameters": {
        "operation": "getAll",
        "calendar": {
          "__rl": true,
          "value": "srmgug2008lngoep8d3o49fcmg@group.calendar.google.com",
          "mode": "list",
          "cachedResultName": "KL Office"
        },
        "limit": 300,
        "options": {
          "timeMin": "={{ $json[\"lastaccess\"] }}"
        }
      },
      "id": "fd943d56-6c08-4f4e-8ce3-4286c05ccb84",
      "name": "Google Calendar Get All Events after lastaccess",
      "type": "n8n-nodes-base.googleCalendar",
      "typeVersion": 1.1,
      "position": [
        -1120,
        720
      ],
      "credentials": {
        "googleCalendarOAuth2Api": {
          "id": "T5W9zRGj16MupgOn",
          "name": "Google Calendar account Yang"
        }
      }
    },
    {
      "parameters": {
        "url": "=http://192.168.1.60:30223/api/v1/appointments/{{$json[\"GoogleID\"]}}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {}
      },
      "id": "9528f643-54d0-457e-b6c0-fb42a525cec2",
      "name": "OD Get Events",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        -680,
        580
      ],
      "alwaysOutputData": true,
      "credentials": {
        "httpHeaderAuth": {
          "id": "c8ZCHq07tFc7C8sw",
          "name": "Header Auth account KL Office"
        }
      },
      "onError": "continueRegularOutput"
    }
  ],
  "connections": {
    "When clicking ‘Test workflow’": {
      "main": [
        [
          {
            "node": "Test Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Restore lastaccess variable",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge2": {
      "main": [
        [
          {
            "node": "Sort",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Test Data": {
      "main": [
        [
          {
            "node": "AptDateTime to UTC",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sort": {
      "main": [
        [
          {
            "node": "Select latest Update",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Select latest Update": {
      "main": [
        [
          {
            "node": "update lastaccess variable",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AptDateTime to UTC": {
      "main": [
        [
          {
            "node": "Google Calendar Get All Events after lastaccess",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Restore lastaccess variable": {
      "main": [
        [
          {
            "node": "AptDateTime to UTC",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Delete and Trim Apt ID Prefix, filter OD events": {
      "main": [
        [
          {
            "node": "OD Get Events",
            "type": "main",
            "index": 0
          },
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Merge": {
      "main": [
        [
          {
            "node": "OD Apt Status?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Event Cancelled?": {
      "main": [
        [
          {
            "node": "Try undelete marked Google events marked cancelled1",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Merge2",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Try undelete marked Google events marked cancelled1": {
      "main": [
        [
          {
            "node": "Merge2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OD Apt Status?": {
      "main": [
        [
          {
            "node": "Google Event Cancelled?",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Google Event Cancelled?",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Google Calendar",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Google Calendar",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Google Calendar",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "No Operation, do nothing",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "No Operation, do nothing",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Calendar Get All Events after lastaccess": {
      "main": [
        [
          {
            "node": "Delete and Trim Apt ID Prefix, filter OD events",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OD Get Events": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {}
}
Comments welcome. This is probably the maximum that is possible without a GUI and real program code.

With best regards

Joerg

Post Reply