-- Monaco USA Portal 2026 - Dues Reminders Enhancement -- Track sent reminders to avoid duplicates and enable analytics -- ============================================ -- DUES REMINDER LOGS TABLE -- ============================================ CREATE TABLE public.dues_reminder_logs ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), member_id UUID NOT NULL REFERENCES public.members(id) ON DELETE CASCADE, reminder_type TEXT NOT NULL CHECK (reminder_type IN ('due_soon_30', 'due_soon_7', 'due_soon_1', 'overdue', 'grace_period', 'inactive_notice')), due_date DATE NOT NULL, sent_at TIMESTAMPTZ DEFAULT NOW(), email_log_id UUID REFERENCES public.email_logs(id), -- Prevent duplicate reminders for same member/type/period UNIQUE(member_id, reminder_type, due_date) ); -- Enable RLS ALTER TABLE public.dues_reminder_logs ENABLE ROW LEVEL SECURITY; -- Board and admin can view reminder logs CREATE POLICY "Board/admin can view reminder logs" ON public.dues_reminder_logs FOR SELECT USING ( EXISTS (SELECT 1 FROM public.members WHERE id = auth.uid() AND role IN ('board', 'admin')) ); -- Only service role can insert reminder logs (from cron/server) CREATE POLICY "Service role can manage reminder logs" ON public.dues_reminder_logs FOR ALL USING (true) WITH CHECK (true); -- Index for fast lookups CREATE INDEX idx_reminder_logs_member_date ON public.dues_reminder_logs(member_id, due_date); CREATE INDEX idx_reminder_logs_type_sent ON public.dues_reminder_logs(reminder_type, sent_at); -- ============================================ -- ADD EMAIL TEMPLATES FOR DUES REMINDERS -- ============================================ -- 30 days before due reminder INSERT INTO public.email_templates (template_key, template_name, category, subject, body_html, body_text, is_active, is_system, variables_schema) VALUES ( 'dues_reminder_30', 'Dues Reminder - 30 Days', 'payment', 'Your Monaco USA Membership Dues Are Coming Up', '
Dear {{first_name}},
This is a friendly reminder that your Monaco USA membership dues will be due on {{due_date}}.
Payment Details:
Amount Due: {{amount}}
Due Date: {{due_date}}
Member ID: {{member_id}}
Bank Transfer Details:
Account Holder: {{account_holder}}
Bank: {{bank_name}}
IBAN: {{iban}}
Reference: {{member_id}}
You can also view your payment status and history in the member portal:
Thank you for being a valued member of Monaco USA!
', 'Dear {{first_name}}, This is a friendly reminder that your Monaco USA membership dues will be due on {{due_date}}. Payment Details: - Amount Due: {{amount}} - Due Date: {{due_date}} - Member ID: {{member_id}} Bank Transfer Details: - Account Holder: {{account_holder}} - Bank: {{bank_name}} - IBAN: {{iban}} - Reference: {{member_id}} Visit the member portal to view your payment status: {{portal_url}} Thank you for being a valued member of Monaco USA!', true, true, '{"first_name": "Member first name", "due_date": "Dues due date", "amount": "Amount due", "member_id": "Member ID for reference", "account_holder": "Bank account holder", "bank_name": "Bank name", "iban": "IBAN number", "portal_url": "Portal URL"}' ), ( 'dues_reminder_7', 'Dues Reminder - 7 Days', 'payment', 'Reminder: Monaco USA Dues Due in 7 Days', 'Dear {{first_name}},
Your Monaco USA membership dues will be due in 7 days on {{due_date}}.
Payment Information:
Amount: {{amount}}
Due Date: {{due_date}}
IBAN: {{iban}}
Reference: {{member_id}}
Questions? Contact us at contact@monacousa.org
', 'Dear {{first_name}}, Your Monaco USA membership dues will be due in 7 days on {{due_date}}. Amount: {{amount}} IBAN: {{iban}} Reference: {{member_id}} Visit the portal to pay: {{portal_url}}', true, true, '{"first_name": "Member first name", "due_date": "Dues due date", "amount": "Amount due", "member_id": "Member ID", "iban": "IBAN number", "portal_url": "Portal URL"}' ), ( 'dues_reminder_1', 'Dues Reminder - 1 Day', 'payment', 'URGENT: Monaco USA Dues Due Tomorrow', 'Dear {{first_name}},
Your Monaco USA membership dues are due tomorrow ({{due_date}}).
Payment Required:
Amount: {{amount}}
IBAN: {{iban}}
Reference: {{member_id}}
To maintain your active membership status and continued access to member benefits, please ensure payment is made by the due date.
', 'Dear {{first_name}}, URGENT: Your Monaco USA membership dues are due tomorrow ({{due_date}}). Amount: {{amount}} IBAN: {{iban}} Reference: {{member_id}} To maintain your active membership, please pay by the due date. Pay now: {{portal_url}}', true, true, '{"first_name": "Member first name", "due_date": "Dues due date", "amount": "Amount due", "member_id": "Member ID", "iban": "IBAN number", "portal_url": "Portal URL"}' ), ( 'dues_overdue', 'Dues Overdue Notice', 'payment', 'ACTION REQUIRED: Monaco USA Dues Are Now Overdue', 'Dear {{first_name}},
Your Monaco USA membership dues are now {{days_overdue}} days overdue.
Overdue Payment:
Amount: {{amount}}
Original Due Date: {{due_date}}
Days Overdue: {{days_overdue}}
Grace Period: You have {{grace_days_remaining}} days remaining in your grace period. After this, your membership status will be changed to inactive.
Please remit payment as soon as possible to maintain your membership benefits.
Payment Details:
Account: {{account_holder}}
IBAN: {{iban}}
Reference: {{member_id}}
Dear {{first_name}},
Your grace period ends in {{grace_days_remaining}} days.
Your membership dues of {{amount}} were due on {{due_date}} and are now {{days_overdue}} days overdue.
If payment is not received by {{grace_end_date}}, your membership status will automatically change to INACTIVE and you will lose access to member benefits.
Please make your payment immediately to avoid interruption:
IBAN: {{iban}}
Reference: {{member_id}}
Dear {{first_name}},
Due to non-payment of membership dues, your Monaco USA membership has been marked as INACTIVE.
Status Change:
Previous Status: Active
New Status: Inactive
Outstanding Amount: {{amount}}
As an inactive member, you will no longer have access to:
To reactivate your membership, please pay your outstanding dues:
Account: {{account_holder}}
IBAN: {{iban}}
Reference: {{member_id}}
If you believe this is an error or have questions, please contact us at contact@monacousa.org
', 'Dear {{first_name}}, Due to non-payment of membership dues, your Monaco USA membership has been marked as INACTIVE. Outstanding Amount: {{amount}} As an inactive member, you no longer have access to member-only events, directory, communications, or voting rights. To reactivate, please pay your dues: - Account: {{account_holder}} - IBAN: {{iban}} - Reference: {{member_id}} Reactivate: {{portal_url}} Questions? Contact contact@monacousa.org', true, true, '{"first_name": "Member first name", "amount": "Outstanding amount", "member_id": "Member ID", "account_holder": "Account holder", "iban": "IBAN", "portal_url": "Portal URL"}' ) ON CONFLICT (template_key) DO NOTHING; -- ============================================ -- HELPER FUNCTION: Get dues settings -- ============================================ CREATE OR REPLACE FUNCTION get_dues_settings() RETURNS TABLE ( reminder_days_before INTEGER[], grace_period_days INTEGER, auto_inactive_enabled BOOLEAN, payment_iban TEXT, payment_account_holder TEXT, payment_bank_name TEXT ) AS $$ BEGIN RETURN QUERY SELECT COALESCE((SELECT (setting_value)::INTEGER[] FROM app_settings WHERE category = 'dues' AND setting_key = 'reminder_days_before'), ARRAY[30, 7, 1])::INTEGER[], COALESCE((SELECT (setting_value)::INTEGER FROM app_settings WHERE category = 'dues' AND setting_key = 'grace_period_days'), 30)::INTEGER, COALESCE((SELECT (setting_value)::BOOLEAN FROM app_settings WHERE category = 'dues' AND setting_key = 'auto_inactive_enabled'), true)::BOOLEAN, COALESCE((SELECT setting_value::TEXT FROM app_settings WHERE category = 'dues' AND setting_key = 'payment_iban'), '')::TEXT, COALESCE((SELECT setting_value::TEXT FROM app_settings WHERE category = 'dues' AND setting_key = 'payment_account_holder'), '')::TEXT, COALESCE((SELECT setting_value::TEXT FROM app_settings WHERE category = 'dues' AND setting_key = 'payment_bank_name'), '')::TEXT; END; $$ LANGUAGE plpgsql STABLE;