diff --git a/src/index.js b/src/index.js index 3ae5b71..0e67195 100644 --- a/src/index.js +++ b/src/index.js @@ -426,4 +426,83 @@ app.notFound(c => { return c.html(`404 Not Found

404

Page not found

Go Home`, 404); }); -export default app; +// ── SCHEDULED: daily document expiry notifications ─────────────────────────── +async function scheduled(event, env, ctx) { + const today = new Date(); + const in30 = new Date(today); in30.setDate(in30.getDate() + 30); + const in7 = new Date(today); in7.setDate(in7.getDate() + 7); + + // Get all expiring documents with user details + const { results } = await env.DB.prepare(` + SELECT d.id, d.doc_label, d.expiry_date, d.user_id, + u.full_name, u.email, u.phone + FROM document_expiries d + JOIN users u ON u.id = d.user_id + WHERE d.expiry_date <= ? + ORDER BY d.expiry_date ASC + `).bind(in30.toISOString().split('T')[0]).all(); + + if (!results || results.length === 0) return; + + // Group by urgency + const expired = results.filter(d => new Date(d.expiry_date) < today); + const urgent = results.filter(d => { const dt = new Date(d.expiry_date); return dt >= today && dt <= in7; }); + const upcoming = results.filter(d => { const dt = new Date(d.expiry_date); return dt > in7 && dt <= in30; }); + + const fmt = d => new Date(d.expiry_date).toLocaleDateString('en-AU', { day: 'numeric', month: 'short', year: 'numeric' }); + const row = d => `${d.doc_label}${d.full_name}${d.email}${d.phone||'—'}${fmt(d)}`; + + const section = (title, color, rows) => rows.length === 0 ? '' : ` +

${title} (${rows.length})

+ + + ${rows.map(row).join('')} +
DocumentClientEmailPhoneExpiry
`; + + const html = ` +
+
+

📄 Daily Document Expiry Report — Careers Gateway

+
${today.toLocaleDateString('en-AU',{weekday:'long',day:'numeric',month:'long',year:'numeric'})}
+
+
+ ${section('🔴 Already Expired', '#dc2626', expired)} + ${section('🟠 Expiring within 7 days', '#d97706', urgent)} + ${section('🟡 Expiring within 30 days', '#ca8a04', upcoming)} +
+ ${results.length} document${results.length!==1?'s':''} across ${new Set(results.map(r=>r.user_id)).size} client${new Set(results.map(r=>r.user_id)).size!==1?'s':''} require attention. + View Dashboard → +
+
+
`; + + await fetch('https://api.mailchannels.net/tx/v1/send', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + personalizations: [{ to: [{ email: env.CONTACT_EMAIL, name: 'Careers Gateway Team' }] }], + from: { email: 'contact@careersgateway.com.au', name: 'Careers Gateway Portal' }, + subject: `📄 ${results.length} document${results.length!==1?'s':''} expiring — Daily Expiry Report`, + content: [{ type: 'text/html', value: html }], + }), + }).catch(() => {}); + + // Also push to Google Sheet for record + if (env.GOOGLE_SHEET_WEBHOOK) { + await fetch(env.GOOGLE_SHEET_WEBHOOK, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + fullName: 'SYSTEM - Daily Expiry Report', + email: env.CONTACT_EMAIL, + phone: '', + service: `📄 Document Expiry Alert: ${results.length} documents`, + message: results.map(d => `${d.doc_label} | ${d.full_name} | ${d.email} | ${fmt(d)}`).join('\n'), + source: 'cron-daily-expiry', + submittedAt: today.toLocaleString('en-AU', { timeZone: 'Australia/Sydney' }), + }), + }).catch(() => {}); + } +} + +export default { fetch: app.fetch, scheduled }; diff --git a/src/visa-data.js b/src/visa-data.js index d8d85e7..98e25ae 100644 --- a/src/visa-data.js +++ b/src/visa-data.js @@ -93,23 +93,25 @@ export function calcPoints(profile) { return { total: pts, breakdown }; } -// ── VAC FEES ───────────────────────────────────────────────────────────────── +// ── VAC FEES — updated 1 July 2025 (2025-26 financial year) ────────────────── +// Source: Department of Home Affairs — immi.homeaffairs.gov.au/visas/fees +// Fees increase annually on 1 July. Always verify before lodging. export const VAC_FEES = [ - { subclass: '189', name: 'Skilled Independent', primary: 4770, secondary: 2385, child: 1195 }, - { subclass: '190', name: 'Skilled Nominated', primary: 4770, secondary: 2385, child: 1195 }, - { subclass: '491', name: 'Skilled Work Regional', primary: 4770, secondary: 2385, child: 1195 }, - { subclass: '186', name: 'Employer Nomination (ENS)', primary: 4770, secondary: 2385, child: 1195 }, - { subclass: '187', name: 'Regional Sponsored (RSMS)', primary: 4770, secondary: 2385, child: 1195 }, - { subclass: '482', name: 'Temporary Skill Shortage', primary: 3115, secondary: 1560, child: 1560 }, - { subclass: '494', name: 'Skilled Employer Sponsored Regional', primary: 4770, secondary: 2385, child: 1195 }, - { subclass: '500', name: 'Student Visa', primary: 710, secondary: 355, child: 355 }, - { subclass: '485', name: 'Graduate Temporary', primary: 1895, secondary: 950, child: 475 }, - { subclass: '820', name: 'Partner (onshore)', primary: 8850, secondary: 4425, child: 0 }, - { subclass: '801', name: 'Partner (permanent onshore)',primary: 0, secondary: 0, child: 0, note: 'No additional fee if applying with 820' }, - { subclass: '309', name: 'Partner (offshore)', primary: 8850, secondary: 4425, child: 0 }, - { subclass: '100', name: 'Partner (permanent offshore)',primary: 0, secondary: 0, child: 0, note: 'No additional fee if applying with 309' }, - { subclass: '600', name: 'Visitor Visa', primary: 190, secondary: 190, child: 190 }, - { subclass: '407', name: 'Training Visa', primary: 335, secondary: 170, child: 170 }, + { subclass: '189', name: 'Skilled Independent', primary: 4990, secondary: 2495, child: 1250 }, + { subclass: '190', name: 'Skilled Nominated', primary: 4990, secondary: 2495, child: 1250 }, + { subclass: '491', name: 'Skilled Work Regional', primary: 4990, secondary: 2495, child: 1250 }, + { subclass: '186', name: 'Employer Nomination (ENS)', primary: 4990, secondary: 2495, child: 1250 }, + { subclass: '187', name: 'Regional Sponsored (RSMS)', primary: 4990, secondary: 2495, child: 1250 }, + { subclass: '482', name: 'Temporary Skill Shortage (TSS)', primary: 3270, secondary: 1635, child: 1635 }, + { subclass: '494', name: 'Skilled Employer Sponsored Regional', primary: 4990, secondary: 2495, child: 1250 }, + { subclass: '500', name: 'Student Visa', primary: 750, secondary: 375, child: 375 }, + { subclass: '485', name: 'Graduate Temporary', primary: 1985, secondary: 995, child: 500 }, + { subclass: '820', name: 'Partner (onshore)', primary: 9265, secondary: 4635, child: 0, note: 'Includes 801 permanent component' }, + { subclass: '801', name: 'Partner (permanent onshore)', primary: 0, secondary: 0, child: 0, note: 'No additional fee if applying with 820' }, + { subclass: '309', name: 'Partner (offshore)', primary: 9265, secondary: 4635, child: 0 }, + { subclass: '100', name: 'Partner (permanent offshore)', primary: 0, secondary: 0, child: 0, note: 'No additional fee if applying with 309' }, + { subclass: '600', name: 'Visitor Visa', primary: 195, secondary: 195, child: 195 }, + { subclass: '407', name: 'Training Visa', primary: 350, secondary: 175, child: 175 }, ]; // ── PROCESSING TIMES ────────────────────────────────────────────────────────── diff --git a/src/visa-tracker.js b/src/visa-tracker.js index efb349d..b7381c4 100644 --- a/src/visa-tracker.js +++ b/src/visa-tracker.js @@ -350,7 +350,12 @@ export function feesPage(user) {
Total VAC
-
Figures in AUD. VAC fees are subject to change; verify at homeaffairs.gov.au before lodging.
+
+ Figures in AUD. Fees current as at 1 July 2025 (2025–26 financial year). + Fees increase annually on 1 July — always + verify at homeaffairs.gov.au + before lodging. +
diff --git a/wrangler.toml b/wrangler.toml index c791107..db32132 100644 --- a/wrangler.toml +++ b/wrangler.toml @@ -3,6 +3,10 @@ main = "src/index.js" compatibility_date = "2024-11-01" compatibility_flags = ["nodejs_compat"] +[triggers] +# Run daily at 9am AEST (23:00 UTC previous day) +crons = ["0 23 * * *"] + [[custom_domains]] pattern = "careers.bored.investments"