ScheduleNotification.ts 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. import 'dotenv/config'
  2. import cron from "node-cron";
  3. import dayjs from "dayjs";
  4. import axios from "axios";
  5. import prisma from "../prisma/PrismaClient";
  6. import { formatISOWithoutTimezone } from './FormatDate';
  7. const queue: (() => Promise<void>)[] = [];
  8. let processing = false;
  9. const API_URL_NOTIFICATION = process.env.API_TOKEN_NOTIFICATION;
  10. const API_TOKEN_NOTIFICATTION = process.env.API_TOKEN_NOTIFICATTION;
  11. const delay = (ms: number) => new Promise((res) => setTimeout(res, ms));
  12. const randomDelay = () => {
  13. const min = 60 * 1000; // 60 detik
  14. const max = 180 * 1000; // 180 detik
  15. return Math.floor(Math.random() * (max - min + 1)) + min;
  16. };
  17. // Worker untuk memproses queue
  18. const processQueue = async () => {
  19. if (processing || queue.length === 0) return;
  20. processing = true;
  21. while (queue.length > 0) {
  22. console.log(`▶️ Mulai memproses antrean, sisa job: ${queue.length}`);
  23. const job = queue.shift();
  24. if (job) {
  25. try {
  26. await job();
  27. if (queue.length > 0) {
  28. const delayMs = randomDelay();
  29. console.log(`⏳ Tunggu ${Math.round(delayMs / 1000)} detik sebelum job berikutnya...`);
  30. await delay(delayMs);
  31. }
  32. } catch (err: any) {
  33. console.error("❌ Job gagal:", err.message);
  34. }
  35. }
  36. }
  37. console.log("✅ Semua job selesai diproses.");
  38. processing = false;
  39. };
  40. // Tambah job ke antrean
  41. const addToQueue = (fn: () => Promise<void>) => {
  42. queue.push(fn);
  43. console.log(`➕ Job ditambahkan ke antrean. Total sekarang: ${queue.length}`);
  44. console.log("📦 Isi antrean saat ini:", queue.map((_, i) => `Job#${i + 1}`));
  45. processQueue();
  46. };
  47. // Cron: jalan tiap hari
  48. cron.schedule("00 08 * * *", async () => {
  49. console.log("⏰ [CRON] Checking schedules for today...");
  50. try {
  51. const today = dayjs().startOf("day").toDate();
  52. const tomorrow = dayjs().add(1, "day").startOf("day").toDate();
  53. // Ambil semua schedule untuk hari ini
  54. const schedules = await prisma.scheduleVisitation.findMany({
  55. where: {
  56. deletedAt: null,
  57. date_visit: {
  58. gte: today,
  59. lt: tomorrow,
  60. },
  61. },
  62. include: {
  63. sales_visit: true,
  64. hospital: {
  65. include: {
  66. province: true,
  67. city: true,
  68. }
  69. }
  70. },
  71. });
  72. if (schedules.length === 0) {
  73. console.log("ℹ️ No schedules for today.");
  74. return;
  75. }
  76. console.log(`📌 Ditemukan ${schedules.length} schedule untuk hari ini:`);
  77. schedules.forEach((s, idx) => {
  78. console.log(` #${idx + 1} SalesID=${s.sales_id}, Phone=${s.sales_visit?.phone ?? "❌ kosong"}`);
  79. });
  80. schedules.forEach((schedule, idx) => {
  81. const phone = schedule.sales_visit?.phone as string | undefined;
  82. if (!phone) {
  83. console.warn(`⚠️ Sales ${schedule.sales_id} tidak punya nomor telepon.`);
  84. return;
  85. }
  86. const fullname = schedule.sales_visit?.fullname || '-';
  87. const hospitalName = schedule.hospital?.name || '-';
  88. const hospitalAddress = schedule.hospital?.address || '-';
  89. const hospitalContact = schedule.hospital?.contact || '-';
  90. const hospitalEmail = schedule.hospital?.email || '-';
  91. const hospitalLat = schedule.hospital?.latitude || '-';
  92. const hospitalLong = schedule.hospital?.longitude || '-';
  93. const hospitalGmaps = schedule.hospital?.gmaps_url || '-';
  94. const hospitalProv = schedule.hospital?.province.name || '-';
  95. const hospitalCity = schedule.hospital?.city.name || '-';
  96. const scheduleCreate = schedule.createdAt || '-';
  97. const message = `
  98. Halo ${fullname},
  99. [[[ maaf, ini sedang testing - gayuh ]]]
  100. Ini adalah pengingat jadwal kunjungan yang dibuat pada tanggal ${formatISOWithoutTimezone(scheduleCreate)}.
  101. Anda dijadwalkan untuk mengunjungi:
  102. Rumah Sakit: ${hospitalName}
  103. Alamat: ${hospitalAddress}
  104. Kota: ${hospitalCity}, Provinsi: ${hospitalProv}
  105. Kontak: ${hospitalContact}, Email: ${hospitalEmail}
  106. Lokasi Google Maps: ${hospitalGmaps}
  107. Koordinat: Latitude ${hospitalLat}, Longitude ${hospitalLong}
  108. Nomor telepon sales terkait: ${phone}
  109. Pastikan semua persiapan kunjungan sudah lengkap! Semangat bertugas!
  110. `.trim();
  111. addToQueue(async () => {
  112. console.log(
  113. `[${dayjs().format("HH:mm:ss")}] 📩 (Job#${idx + 1}) Sending message to ${phone}...`
  114. );
  115. await axios.post(
  116. API_URL_NOTIFICATION!,
  117. { to: phone, message },
  118. {
  119. headers: {
  120. "x-api-token": API_TOKEN_NOTIFICATTION!,
  121. "Content-Type": "application/json",
  122. },
  123. }
  124. );
  125. console.log(
  126. `[${dayjs().format("HH:mm:ss")}] ✔️ (Job#${idx + 1}) Sent message to ${phone}`
  127. );
  128. });
  129. });
  130. } catch (err: any) {
  131. console.error("❌ Cron job error:", err.message);
  132. }
  133. });