-- 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:

View My Account

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}}

Pay Now

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.

Pay Now

', '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}}

Pay Now

', 'Dear {{first_name}}, Your Monaco USA membership dues are now {{days_overdue}} days overdue. Amount: {{amount}} Original Due Date: {{due_date}} Days Overdue: {{days_overdue}} Grace Period: You have {{grace_days_remaining}} days remaining. After this, your membership will be marked inactive. Payment Details: - Account: {{account_holder}} - IBAN: {{iban}} - Reference: {{member_id}} Pay now: {{portal_url}}', true, true, '{"first_name": "Member first name", "due_date": "Original due date", "amount": "Amount due", "days_overdue": "Number of days overdue", "grace_days_remaining": "Days left in grace period", "member_id": "Member ID", "account_holder": "Account holder", "iban": "IBAN", "portal_url": "Portal URL"}' ), ( 'dues_grace_warning', 'Grace Period Ending Warning', 'payment', 'WARNING: Monaco USA Grace Period Ending Soon', '

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}}

Pay Now - Urgent

', 'Dear {{first_name}}, WARNING: Your grace period ends in {{grace_days_remaining}} days. Your 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 will become INACTIVE. IBAN: {{iban}} Reference: {{member_id}} Pay now: {{portal_url}}', true, true, '{"first_name": "Member first name", "due_date": "Original due date", "amount": "Amount due", "days_overdue": "Days overdue", "grace_days_remaining": "Days until grace period ends", "grace_end_date": "Date grace period ends", "member_id": "Member ID", "iban": "IBAN", "portal_url": "Portal URL"}' ), ( 'dues_inactive_notice', 'Membership Marked Inactive', 'payment', 'Notice: Your Monaco USA Membership Is Now Inactive', '

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}}

Reactivate My Membership

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;