{
  "name": "Jen Daily Schedule Alert",
  "nodes": [
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "value": "1B19nehOp-PMweCWRcsyEne5Ih1dztN6Zc4JEp2ZgxsY",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "Sheet1",
          "mode": "name"
        },
        "options": {}
      },
      "id": "cad75dff-5113-4b13-a352-83d579d3967a",
      "name": "\u8b80\u53d6\u6392\u73ed\u8868",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4,
      "position": [
        220,
        0
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Jen \u6392\u73ed\u901a\u77e5\u8655\u7406\u908f\u8f2f - \u4fee\u6b63\u6642\u5340\u5224\u65b7\u908f\u8f2f\uff08\u542b\u590f\u4ee4\u6642\uff09\nconst items = $input.all();\n\nconsole.log('=== \u8cc7\u6599\u7d50\u69cb\u5206\u6790 ===');\nconsole.log('items \u9577\u5ea6:', items.length);\n\n// \ud83d\udd50 \u52d5\u614b\u6642\u5340\u504f\u79fb\u51fd\u6578\uff08\u8655\u7406\u590f\u4ee4\u6642\uff09\nfunction getCalgaryOffset(date) {\n  const year = date.getFullYear();\n  \n  // \u8a08\u7b97\u590f\u4ee4\u6642\u958b\u59cb\uff1a3\u6708\u7b2c\u4e8c\u500b\u9031\u65e5\n  const marchFirst = new Date(year, 2, 1); // 3\u67081\u65e5\n  const firstSunday = 7 - marchFirst.getDay();\n  const secondSunday = firstSunday + 7;\n  const dstStart = new Date(year, 2, secondSunday, 2, 0, 0); // 3\u6708\u7b2c\u4e8c\u500b\u9031\u65e5 2:00 AM\n  \n  // \u8a08\u7b97\u590f\u4ee4\u6642\u7d50\u675f\uff1a11\u6708\u7b2c\u4e00\u500b\u9031\u65e5\n  const novemberFirst = new Date(year, 10, 1); // 11\u67081\u65e5\n  const novFirstSunday = 7 - novemberFirst.getDay();\n  const dstEnd = new Date(year, 10, novFirstSunday, 2, 0, 0); // 11\u6708\u7b2c\u4e00\u500b\u9031\u65e5 2:00 AM\n  \n  // \u5224\u65b7\u662f\u5426\u5728\u590f\u4ee4\u6642\u671f\u9593\n  if (date >= dstStart && date < dstEnd) {\n    console.log('\ud83c\udf1e \u5361\u52a0\u5229\u590f\u4ee4\u6642\u671f\u9593 (MDT): UTC-6');\n    return -6 * 60; // UTC-6 (Mountain Daylight Time)\n  } else {\n    console.log('\u2744\ufe0f \u5361\u52a0\u5229\u6a19\u6e96\u6642\u671f\u9593 (MST): UTC-7');\n    return -7 * 60; // UTC-7 (Mountain Standard Time)\n  }\n}\n\n// \u6642\u5340\u8a2d\u7f6e\nconst taipeiOffset = 8 * 60; // UTC+8 (\u5206\u9418)\nconst now = new Date();\nconst calgaryOffset = getCalgaryOffset(now); // \u52d5\u614b\u8a08\u7b97\u5361\u52a0\u5229\u6642\u5340\n\n// \u53d6\u5f97\u7576\u524d\u6642\u9593\nconst taipeiNow = new Date(now.getTime() + (taipeiOffset * 60000));\nconst calgaryNow = new Date(now.getTime() + (calgaryOffset * 60000));\n\nconsole.log('\ud83c\udf0d \u6642\u5340\u8cc7\u8a0a:');\nconsole.log('\u53f0\u5317\u6642\u5340\u504f\u79fb:', taipeiOffset / 60, '\u5c0f\u6642');\nconsole.log('\u5361\u52a0\u5229\u6642\u5340\u504f\u79fb:', calgaryOffset / 60, '\u5c0f\u6642');\n\n// \ud83c\udfaf \u65b0\u7684\u6642\u9593\u5224\u65b7\u908f\u8f2f\nconst taipeiHour = taipeiNow.getHours();\nlet targetDate;\nlet displayDay;\nlet isShowingTomorrow;\n\nif (taipeiHour >= 8 && taipeiHour <= 19) {\n  // \u53f0\u5317 08:00-19:59 \u2192 \u986f\u793a\u5361\u52a0\u5229\u300c\u660e\u5929\u300d\u73ed\u8868\n  const calgaryTomorrow = new Date(calgaryNow);\n  calgaryTomorrow.setDate(calgaryTomorrow.getDate() + 1);\n  targetDate = calgaryTomorrow.toISOString().split('T')[0];\n  displayDay = '\u660e\u5929';\n  isShowingTomorrow = true;\n  console.log('\ud83c\udf05 \u53f0\u5317\u767d\u5929\u6642\u6bb5 (08:00-19:59) - \u986f\u793a\u5361\u52a0\u5229\u660e\u5929\u73ed\u8868');\n} else {\n  // \u53f0\u5317 20:00-07:59 \u2192 \u986f\u793a\u5361\u52a0\u5229\u300c\u4eca\u5929\u300d\u73ed\u8868\n  targetDate = calgaryNow.toISOString().split('T')[0];\n  displayDay = '\u4eca\u5929';\n  isShowingTomorrow = false;\n  console.log('\ud83c\udf19 \u53f0\u5317\u591c\u9593\u6642\u6bb5 (20:00-07:59) - \u986f\u793a\u5361\u52a0\u5229\u4eca\u5929\u73ed\u8868');\n}\n\nconsole.log('\u53f0\u5317\u7576\u524d\u6642\u9593:', taipeiNow.toISOString());\nconsole.log('\u5361\u52a0\u5229\u7576\u524d\u6642\u9593:', calgaryNow.toISOString());\nconsole.log('\u76ee\u6a19\u65e5\u671f:', targetDate);\nconsole.log('\u986f\u793a\u5929\u6578:', displayDay);\nconsole.log('\u53f0\u5317\u5c0f\u6642:', taipeiHour);\n\n// \u6536\u96c6\u6240\u6709\u8cc7\u6599\nconst scheduleData = [];\n\nconsole.log('\u958b\u59cb\u8655\u7406\u6240\u6709 items...');\n\n// \u76f4\u63a5\u8655\u7406\u6bcf\u500b item\uff08\u6bcf\u500b item \u4ee3\u8868\u4e00\u884c\u8cc7\u6599\uff09\nfor (let i = 0; i < items.length; i++) {\n  const item = items[i];\n  const row = item.json;\n  \n  console.log(`\u8655\u7406\u7b2c ${i+1} \u500b item:`, JSON.stringify(row, null, 2));\n  \n  // \u8df3\u904e\u7121\u6548\u8cc7\u6599\n  if (!row || !row.Employee || !row.Date) {\n    console.log(`\u8df3\u904e\u7b2c ${i+1} \u500b item (\u7121\u54e1\u5de5\u540d\u7a31\u6216\u65e5\u671f)`);\n    continue;\n  }\n  \n  const employee = row.Employee;\n  const dateStr = row.Date;\n  const day = row.Day;\n  const shiftTime = row.Shift_Time;\n  const status = row.Status;\n  const location = row.Manager_Location;\n  \n  console.log(`\u8655\u7406\u54e1\u5de5: ${employee}, \u65e5\u671f: ${dateStr}, \u73ed\u6b21: ${shiftTime}, \u72c0\u614b: ${status}, \u4f4d\u7f6e: ${location}`);\n  \n  // \u65e5\u671f\u683c\u5f0f\u8f49\u63db\n  let formattedDate = '';\n  if (dateStr && dateStr.includes('/')) {\n    try {\n      const parts = dateStr.split('/');\n      if (parts.length === 3) {\n        const month = parts[0].padStart(2, '0');\n        const day = parts[1].padStart(2, '0');\n        const year = parts[2];\n        formattedDate = `${year}-${month}-${day}`;\n        console.log(`\u65e5\u671f\u8f49\u63db: ${dateStr} \u2192 ${formattedDate}`);\n      }\n    } catch (e) {\n      console.log('\u65e5\u671f\u89e3\u6790\u932f\u8aa4:', dateStr);\n      continue;\n    }\n  }\n  \n  if (formattedDate && employee) {\n    const processedRow = {\n      employee: employee.trim(),\n      date: formattedDate,\n      day: day,\n      shiftTime: shiftTime || '',\n      status: status || '',\n      location: location || ''\n    };\n    \n    scheduleData.push(processedRow);\n    console.log(`\u2705 \u6210\u529f\u8655\u7406:`, processedRow);\n  }\n}\n\nconsole.log('\u8655\u7406\u5f8c\u7684 scheduleData \u9577\u5ea6:', scheduleData.length);\nconsole.log('\u524d5\u7b46\u8655\u7406\u5f8c\u7684\u8cc7\u6599:', scheduleData.slice(0, 5));\n\n// \u627e\u51fa\u76ee\u6a19\u65e5\u671f\u7684\u73ed\u8868\nconst targetSchedules = scheduleData.filter(item => \n  item.date === targetDate\n);\n\nconsole.log(`${displayDay}\u7684\u73ed\u8868\u7b46\u6578:`, targetSchedules.length);\nconsole.log(`${displayDay}\u7684\u6240\u6709\u73ed\u8868:`, targetSchedules);\n\n// \u627e\u51fa Jen \u7684\u73ed\u8868\nconst jenTargetSchedule = targetSchedules.find(item => \n  item.employee && item.employee.toLowerCase().includes('jen')\n);\n\n// \u627e\u51fa\u5176\u4ed6\u540c\u4e8b\u7684\u73ed\u8868\nconst othersTargetSchedules = targetSchedules.filter(item => \n  item.employee && !item.employee.toLowerCase().includes('jen')\n);\n\nconsole.log(`Jen ${displayDay}\u73ed\u8868:`, jenTargetSchedule);\nconsole.log(`\u5176\u4ed6\u540c\u4e8b${displayDay}\u73ed\u8868\u6578\u91cf:`, othersTargetSchedules.length);\n\n// \u683c\u5f0f\u5316\u901a\u77e5\u8a0a\u606f - \u4fee\u6b63\u4f5c\u7528\u57df\u554f\u984c\nfunction formatScheduleMessage(taipeiNow, calgaryNow, jenSchedule, othersSchedules, displayDay, targetDate, isShowingTomorrow, calgaryOffset) {\n  const taipeiDateStr = taipeiNow.toLocaleDateString('zh-TW', {\n    year: 'numeric',\n    month: 'numeric', \n    day: 'numeric',\n    weekday: 'short'\n  });\n  \n  const taipeiTimeStr = taipeiNow.toLocaleTimeString('zh-TW', {\n    hour: '2-digit',\n    minute: '2-digit',\n    hour12: false\n  });\n  \n  const calgaryDateStr = calgaryNow.toLocaleDateString('en-CA', {\n    year: 'numeric',\n    month: 'numeric',\n    day: 'numeric', \n    weekday: 'short'\n  });\n  \n  const calgaryTimeStr = calgaryNow.toLocaleTimeString('en-CA', {\n    hour: '2-digit',\n    minute: '2-digit',\n    hour12: true\n  });\n  \n  // \u8a08\u7b97\u76ee\u6a19\u65e5\u671f\u7684\u986f\u793a\u683c\u5f0f\n  const targetDateObj = new Date(targetDate + 'T12:00:00');\n  const targetDateStr = targetDateObj.toLocaleDateString('zh-TW', {\n    month: 'numeric',\n    day: 'numeric', \n    weekday: 'short'\n  });\n  \n  // \u986f\u793a\u6642\u5340\u8cc7\u8a0a\n  const timeZoneInfo = calgaryOffset === -6 * 60 ? 'MDT (UTC-6)' : 'MST (UTC-7)';\n  \n  let message = `\ud83c\udf41 Jen ${displayDay}\u73ed\u8868\u63d0\u9192\\n\\n`;\n  message += `\u26a0\ufe0f \u6642\u5340\u63d0\u9192\uff1a\u9019\u662f Jen \u5728\u5361\u52a0\u5229\u7684\u300c${displayDay}\u300d\u73ed\u8868\uff01\\n`;\n  message += `\ud83d\udcc5 \u53f0\u5317\u73fe\u5728\uff1a${taipeiDateStr} ${taipeiTimeStr}\\n`;\n  message += `\ud83d\udcc5 \u5361\u52a0\u5229\u73fe\u5728\uff1a${calgaryDateStr} ${calgaryTimeStr} (${timeZoneInfo})\\n\\n`;\n  \n  // Jen \u7684\u73ed\u8868\n  if (jenSchedule) {\n    message += `\ud83d\udc64 Jen \u7684\u5361\u52a0\u5229${displayDay}\u73ed\u6b21 (${targetDateStr})\uff1a\\n`;\n    \n    if (jenSchedule.status === 'OFF') {\n      message += `\ud83c\udfe0 \u4f11\u5047\\n`;\n    } else {\n      const timeInfo = jenSchedule.shiftTime;\n      if (timeInfo) {\n        message += `\u23f0 ${timeInfo} (\u5361\u52a0\u5229\u6642\u9593)\\n`;\n      }\n      message += `\ud83d\udccd \u72c0\u614b: ${jenSchedule.status}\\n`;\n      if (jenSchedule.location) {\n        message += `\ud83d\udccd \u4f4d\u7f6e: ${jenSchedule.location}\\n`;\n      }\n    }\n  } else {\n    message += `\ud83d\udc64 Jen \u7684\u5361\u52a0\u5229${displayDay}\u73ed\u6b21 (${targetDateStr})\uff1a\\n`;\n    message += `\u2753 \u67e5\u7121\u8cc7\u6599\\n`;\n  }\n  \n  message += `\\n\ud83d\udc65 \u540c\u65e5\u5176\u4ed6\u540c\u4e8b\uff1a\\n`;\n  \n  if (othersSchedules.length > 0) {\n    othersSchedules.forEach(schedule => {\n      const employeeName = schedule.employee;\n      \n      // Karen \u7684\u908f\u8f2f\uff1a\u6839\u64da Manager_Location \u986f\u793a\u4f4d\u7f6e\n      if (employeeName.toLowerCase().includes('karen')) {\n        if (schedule.location) {\n          message += `\u2022 ${employeeName}: Manager (${schedule.location})\\n`;\n        } else {\n          message += `\u2022 ${employeeName}: Manager\\n`;\n        }\n      } else {\n        // \u5176\u4ed6\u54e1\u5de5\u7684\u908f\u8f2f\n        if (schedule.status === 'OFF') {\n          message += `\u2022 ${employeeName}: \u4f11\u5047\\n`;\n        } else {\n          const timeInfo = schedule.shiftTime || schedule.status;\n          const locationInfo = schedule.location ? ` (${schedule.location})` : '';\n          message += `\u2022 ${employeeName}: ${timeInfo}${locationInfo}\\n`;\n        }\n      }\n    });\n  } else {\n    message += `\u2022 \u67e5\u7121\u5176\u4ed6\u540c\u4e8b\u8cc7\u6599\\n`;\n  }\n  \n  return message;\n}\n\n// \u547c\u53eb\u683c\u5f0f\u5316\u51fd\u5f0f\uff0c\u50b3\u5165\u6240\u9700\u7684\u53c3\u6578\nconst notificationMessage = formatScheduleMessage(\n  taipeiNow, \n  calgaryNow, \n  jenTargetSchedule, \n  othersTargetSchedules,\n  displayDay,\n  targetDate,\n  isShowingTomorrow,\n  calgaryOffset\n);\n\nconsole.log('\u6700\u7d42\u901a\u77e5\u8a0a\u606f:', notificationMessage);\n\n// \u56de\u50b3\u8655\u7406\u7d50\u679c\nreturn {\n  json: {\n    message: notificationMessage,\n    jenSchedule: jenTargetSchedule,\n    othersSchedules: othersTargetSchedules,\n    targetDate: targetDate,\n    displayDay: displayDay,\n    isShowingTomorrow: isShowingTomorrow,\n    taipeiTime: taipeiNow.toISOString(),\n    calgaryTime: calgaryNow.toISOString(),\n    timeZoneInfo: {\n      taipeiOffset: taipeiOffset / 60,\n      calgaryOffset: calgaryOffset / 60,\n      isDST: calgaryOffset === -6 * 60,\n      timeZoneDisplay: calgaryOffset === -6 * 60 ? 'MDT (UTC-6)' : 'MST (UTC-7)'\n    },\n    debugInfo: {\n      totalItems: items.length,\n      processedDataLength: scheduleData.length,\n      targetSchedulesLength: targetSchedules.length,\n      jenFound: !!jenTargetSchedule,\n      taipeiHour: taipeiHour\n    }\n  }\n};"
      },
      "id": "f5dc57ad-22cd-48fc-abea-5ea9c5cbd196",
      "name": "\u8655\u7406\u6392\u73ed\u8cc7\u6599",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        440,
        0
      ]
    },
    {
      "parameters": {
        "channel": "#n8n-work-schedule",
        "text": "={{$json.message}}",
        "otherOptions": {},
        "attachments": []
      },
      "id": "bd7cea7e-3893-4e1d-8142-1ee6051f83fc",
      "name": "\u767c\u9001 Slack \u901a\u77e5",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 1,
      "position": [
        660,
        0
      ],
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 8,20 * * *"
            }
          ]
        }
      },
      "id": "9ad9e047-9354-4488-b891-0fd997553866",
      "name": "\u6bcf\u65e5\u65e9\u665a 8:00 \u89f8\u767c",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.1,
      "position": [
        0,
        0
      ]
    }
  ],
  "connections": {
    "\u8b80\u53d6\u6392\u73ed\u8868": {
      "main": [
        [
          {
            "node": "\u8655\u7406\u6392\u73ed\u8cc7\u6599",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\u8655\u7406\u6392\u73ed\u8cc7\u6599": {
      "main": [
        [
          {
            "node": "\u767c\u9001 Slack \u901a\u77e5",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\u6bcf\u65e5\u65e9\u665a 8:00 \u89f8\u767c": {
      "main": [
        [
          {
            "node": "\u8b80\u53d6\u6392\u73ed\u8868",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": true,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "d6d750dc-23d3-4723-9a5e-9773b89132d5",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "id": "IpjZzAXLRwSjcLza",
  "tags": []
}