-- BookingSystem complete clean-install schema, phpMyAdmin-safe edition.
-- Generated and tested against MariaDB 10.4.32 on 2026-09-27.
-- Import once into a NEW, EMPTY database using utf8mb4_unicode_ci.
-- Contains 127 tables, required indexes/constraints/views/triggers, reference data,
-- and migration history through 202609280030.
-- Contains no MySQL accounts, application users, Provinces, units, bookings,
-- credentials, passwords, DROP DATABASE, stored procedures, or CALL statements.

/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `access_reviews` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `membership_id` bigint(20) unsigned NOT NULL,
  `reviewer_id` bigint(20) unsigned NOT NULL,
  `scope_type` enum('platform','province','unit') NOT NULL,
  `scope_id` bigint(20) unsigned NOT NULL,
  `role_key` varchar(50) NOT NULL,
  `decision` enum('retain','revoke') NOT NULL,
  `previous_expires_at` datetime DEFAULT NULL,
  `expires_at` datetime DEFAULT NULL,
  `next_review_at` datetime DEFAULT NULL,
  `reviewed_at` datetime NOT NULL DEFAULT current_timestamp(),
  PRIMARY KEY (`id`),
  KEY `idx_review_scope` (`scope_type`,`scope_id`,`reviewed_at`),
  KEY `membership_id` (`membership_id`),
  KEY `reviewer_id` (`reviewer_id`),
  CONSTRAINT `access_reviews_ibfk_1` FOREIGN KEY (`membership_id`) REFERENCES `user_memberships` (`id`),
  CONSTRAINT `access_reviews_ibfk_2` FOREIGN KEY (`reviewer_id`) REFERENCES `users` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `account_mfa` (
  `user_id` bigint(20) unsigned NOT NULL,
  `secret_cipher` text DEFAULT NULL,
  `enabled_at` datetime DEFAULT NULL,
  `last_counter` bigint(20) NOT NULL DEFAULT -1,
  `pending_cipher` text DEFAULT NULL,
  `pending_expires_at` datetime DEFAULT NULL,
  `pending_session_hash` char(64) DEFAULT NULL,
  PRIMARY KEY (`user_id`),
  CONSTRAINT `account_mfa_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `account_recovery_codes` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `user_id` bigint(20) unsigned NOT NULL,
  `code_hash` char(64) NOT NULL,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  `used_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_recovery_code` (`user_id`,`code_hash`),
  CONSTRAINT `account_recovery_codes_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
SET @saved_cs_client     = @@character_set_client;
SET character_set_client = utf8;
/*!50001 CREATE VIEW `active_user_memberships` AS SELECT
 1 AS `id`,
  1 AS `user_id`,
  1 AS `scope_type`,
  1 AS `province_id`,
  1 AS `unit_id`,
  1 AS `role`,
  1 AS `active`,
  1 AS `created_at`,
  1 AS `updated_at`,
  1 AS `scope_key`,
  1 AS `expires_at`,
  1 AS `revoked_at`,
  1 AS `last_used_at`,
  1 AS `reviewed_at`,
  1 AS `review_due_at` */;
SET character_set_client = @saved_cs_client;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `activity_log` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `source_kind` enum('unit','province') NOT NULL,
  `source_id` bigint(20) unsigned NOT NULL,
  `actor_id` bigint(20) unsigned DEFAULT NULL,
  `province_id` bigint(20) unsigned DEFAULT NULL,
  `unit_id` bigint(20) unsigned DEFAULT NULL,
  `action` varchar(100) NOT NULL,
  `target_type` varchar(50) DEFAULT NULL,
  `target_id` bigint(20) unsigned DEFAULT NULL,
  `change_summary` varchar(250) NOT NULL,
  `occurred_at` datetime NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_activity_source` (`source_kind`,`source_id`),
  KEY `idx_activity_unit` (`unit_id`,`occurred_at`,`id`),
  KEY `idx_activity_province` (`province_id`,`occurred_at`,`id`),
  KEY `idx_activity_actor` (`actor_id`,`occurred_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!50003 SET @saved_cs_client      = @@character_set_client */ ;
/*!50003 SET @saved_cs_results     = @@character_set_results */ ;
/*!50003 SET @saved_col_connection = @@collation_connection */ ;
/*!50003 SET character_set_client  = utf8mb4 */ ;
/*!50003 SET character_set_results = utf8mb4 */ ;
/*!50003 SET collation_connection  = utf8mb4_general_ci */ ;
/*!50003 SET @saved_sql_mode       = @@sql_mode */ ;
/*!50003 SET sql_mode              = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ;
DELIMITER ;;
/*!50003 CREATE*/ /*!50003 TRIGGER trg_activity_immutable_update BEFORE UPDATE ON activity_log FOR EACH ROW
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Activity history is append-only' */;;
DELIMITER ;
/*!50003 SET sql_mode              = @saved_sql_mode */ ;
/*!50003 SET character_set_client  = @saved_cs_client */ ;
/*!50003 SET character_set_results = @saved_cs_results */ ;
/*!50003 SET collation_connection  = @saved_col_connection */ ;
/*!50003 SET @saved_cs_client      = @@character_set_client */ ;
/*!50003 SET @saved_cs_results     = @@character_set_results */ ;
/*!50003 SET @saved_col_connection = @@collation_connection */ ;
/*!50003 SET character_set_client  = utf8mb4 */ ;
/*!50003 SET character_set_results = utf8mb4 */ ;
/*!50003 SET collation_connection  = utf8mb4_general_ci */ ;
/*!50003 SET @saved_sql_mode       = @@sql_mode */ ;
/*!50003 SET sql_mode              = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ;
DELIMITER ;;
/*!50003 CREATE*/ /*!50003 TRIGGER trg_activity_immutable_delete BEFORE DELETE ON activity_log FOR EACH ROW
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Activity history is append-only' */;;
DELIMITER ;
/*!50003 SET sql_mode              = @saved_sql_mode */ ;
/*!50003 SET character_set_client  = @saved_cs_client */ ;
/*!50003 SET character_set_results = @saved_cs_results */ ;
/*!50003 SET collation_connection  = @saved_col_connection */ ;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `administrator_sessions` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `user_id` bigint(20) unsigned NOT NULL,
  `session_hash` char(64) NOT NULL,
  `ip_address` varchar(45) DEFAULT NULL,
  `user_agent` varchar(500) DEFAULT NULL,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  `last_seen_at` datetime NOT NULL DEFAULT current_timestamp(),
  `expires_at` datetime NOT NULL,
  `revoked_at` datetime DEFAULT NULL,
  `auth_strength` enum('password','email','totp','recovery') NOT NULL DEFAULT 'password',
  `reauthenticated_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_administrator_session_hash` (`session_hash`),
  KEY `idx_administrator_session_user` (`user_id`,`last_seen_at`),
  CONSTRAINT `fk_administrator_session_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `attendee_contacts` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `unit_id` bigint(20) unsigned NOT NULL,
  `salutation` varchar(30) NOT NULL,
  `first_name` varchar(100) NOT NULL,
  `surname` varchar(100) NOT NULL,
  `email` varchar(254) NOT NULL,
  `phone` varchar(30) DEFAULT NULL,
  `masonic_rank` varchar(150) DEFAULT NULL,
  `provincial_rank` varchar(150) DEFAULT NULL,
  `grand_rank` varchar(150) DEFAULT NULL,
  `lodge_name` varchar(150) DEFAULT NULL,
  `lodge_number` varchar(30) DEFAULT NULL,
  `consented_at` datetime NOT NULL,
  `last_booked_at` datetime NOT NULL,
  `consent_withdrawn_at` datetime DEFAULT NULL,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  `updated_at` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_attendee_contact_unit_email` (`unit_id`,`email`),
  KEY `idx_attendee_contact_consent` (`consent_withdrawn_at`,`last_booked_at`),
  KEY `idx_attendee_contacts_unit` (`unit_id`),
  CONSTRAINT `fk_attendee_contacts_unit` FOREIGN KEY (`unit_id`) REFERENCES `units` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `attendee_invitations` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `attendee_contact_id` bigint(20) unsigned NOT NULL,
  `meeting_id` bigint(20) unsigned NOT NULL,
  `status` enum('queued','sent','failed') NOT NULL,
  `sent_at` datetime DEFAULT NULL,
  `failure_message` text DEFAULT NULL,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  `updated_at` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_attendee_meeting_invitation` (`attendee_contact_id`,`meeting_id`),
  KEY `fk_attendee_invitation_meeting` (`meeting_id`),
  CONSTRAINT `fk_attendee_invitation_contact` FOREIGN KEY (`attendee_contact_id`) REFERENCES `attendee_contacts` (`id`) ON DELETE CASCADE,
  CONSTRAINT `fk_attendee_invitation_meeting` FOREIGN KEY (`meeting_id`) REFERENCES `meetings` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `audit_log` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `unit_id` bigint(20) unsigned DEFAULT NULL,
  `user_id` bigint(20) unsigned DEFAULT NULL,
  `action` varchar(100) NOT NULL,
  `entity_type` varchar(50) DEFAULT NULL,
  `entity_id` bigint(20) unsigned DEFAULT NULL,
  `booking_reference` varchar(50) DEFAULT NULL,
  `details` text DEFAULT NULL,
  `ip_address` varchar(45) DEFAULT NULL,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  PRIMARY KEY (`id`),
  KEY `idx_audit_created` (`created_at`),
  KEY `idx_audit_reference` (`booking_reference`),
  KEY `fk_audit_user` (`user_id`),
  KEY `idx_audit_unit_created` (`unit_id`,`created_at`),
  CONSTRAINT `fk_audit_unit` FOREIGN KEY (`unit_id`) REFERENCES `units` (`id`) ON DELETE SET NULL,
  CONSTRAINT `fk_audit_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!50003 SET @saved_cs_client      = @@character_set_client */ ;
/*!50003 SET @saved_cs_results     = @@character_set_results */ ;
/*!50003 SET @saved_col_connection = @@collation_connection */ ;
/*!50003 SET character_set_client  = utf8mb4 */ ;
/*!50003 SET character_set_results = utf8mb4 */ ;
/*!50003 SET collation_connection  = utf8mb4_general_ci */ ;
/*!50003 SET @saved_sql_mode       = @@sql_mode */ ;
/*!50003 SET sql_mode              = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ;
DELIMITER ;;
/*!50003 CREATE*/ /*!50003 TRIGGER trg_activity_from_unit AFTER INSERT ON audit_log FOR EACH ROW
INSERT INTO activity_log(source_kind,source_id,actor_id,province_id,unit_id,action,target_type,target_id,change_summary,occurred_at)
VALUES('unit',NEW.id,NEW.user_id,(SELECT u.province_id FROM units u WHERE u.id=NEW.unit_id),NEW.unit_id,
NEW.action,NEW.entity_type,NEW.entity_id,REPLACE(NEW.action,'_',' '),NEW.created_at) */;;
DELIMITER ;
/*!50003 SET sql_mode              = @saved_sql_mode */ ;
/*!50003 SET character_set_client  = @saved_cs_client */ ;
/*!50003 SET character_set_results = @saved_cs_results */ ;
/*!50003 SET collation_connection  = @saved_col_connection */ ;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `auth_rate_limits` (
  `bucket` char(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `attempts` int(10) unsigned NOT NULL DEFAULT 0,
  `window_started` datetime NOT NULL,
  PRIMARY KEY (`bucket`),
  KEY `idx_auth_window_started` (`window_started`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `backup_log` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `unit_id` bigint(20) unsigned NOT NULL,
  `filename` varchar(255) NOT NULL,
  `size_bytes` bigint(20) unsigned NOT NULL DEFAULT 0,
  `status` enum('success','failed') NOT NULL,
  `message` text DEFAULT NULL,
  `created_by` bigint(20) unsigned DEFAULT NULL,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  PRIMARY KEY (`id`),
  KEY `fk_backup_user` (`created_by`),
  KEY `idx_backup_log_unit` (`unit_id`),
  CONSTRAINT `fk_backup_log_unit` FOREIGN KEY (`unit_id`) REFERENCES `units` (`id`),
  CONSTRAINT `fk_backup_user` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `booking_history` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `booking_id` bigint(20) unsigned NOT NULL,
  `changed_by_user_id` bigint(20) unsigned DEFAULT NULL,
  `changed_by_type` enum('attendee','officer','system') NOT NULL DEFAULT 'system',
  `field_name` varchar(100) NOT NULL,
  `old_value` text DEFAULT NULL,
  `new_value` text DEFAULT NULL,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  PRIMARY KEY (`id`),
  KEY `idx_history_booking` (`booking_id`,`created_at`),
  KEY `fk_history_user` (`changed_by_user_id`),
  CONSTRAINT `fk_history_booking` FOREIGN KEY (`booking_id`) REFERENCES `bookings` (`id`) ON DELETE CASCADE,
  CONSTRAINT `fk_history_user` FOREIGN KEY (`changed_by_user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `bookings` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `meeting_id` bigint(20) unsigned NOT NULL,
  `booking_reference` varchar(50) NOT NULL,
  `booking_code_hash` varchar(255) NOT NULL,
  `booking_code_lookup` char(64) DEFAULT NULL,
  `status` enum('active','cancelled','waiting','offered','declined') NOT NULL DEFAULT 'active',
  `attendance` enum('meeting_and_festive_board','meeting_only','apology') NOT NULL,
  `salutation` varchar(30) NOT NULL,
  `first_name` varchar(100) NOT NULL,
  `surname` varchar(100) NOT NULL,
  `email` varchar(254) NOT NULL,
  `phone` varchar(30) DEFAULT NULL,
  `future_contact_consent` tinyint(1) NOT NULL DEFAULT 0,
  `is_lodge_member` tinyint(1) NOT NULL DEFAULT 0,
  `attendee_type` enum('lodge_member','visiting_member','special_event_guest','ladies_evening_guest','white_table_guest','blue_table_guest') NOT NULL DEFAULT 'visiting_member',
  `attendee_type_label` varchar(120) DEFAULT NULL,
  `lodge_name` varchar(150) DEFAULT NULL,
  `lodge_number` varchar(30) DEFAULT NULL,
  `masonic_rank` varchar(150) DEFAULT NULL,
  `provincial_rank` varchar(150) DEFAULT NULL,
  `grand_rank` varchar(150) DEFAULT NULL,
  `dining` tinyint(1) NOT NULL DEFAULT 0,
  `dietary_requirements` text DEFAULT NULL,
  `allergy_details` text DEFAULT NULL,
  `notes` text DEFAULT NULL,
  `payment_selection` enum('pay','provincial_visit_pay','grand_lodge_visit_pay','provincial_visit','grand_lodge_visit','not_required') NOT NULL DEFAULT 'not_required',
  `dining_subtotal` decimal(10,2) NOT NULL DEFAULT 0.00,
  `raffle_ticket_quantity` int(10) unsigned NOT NULL DEFAULT 0,
  `raffle_ticket_unit_price` decimal(10,2) NOT NULL DEFAULT 0.00,
  `raffle_subtotal` decimal(10,2) NOT NULL DEFAULT 0.00,
  `total_charge` decimal(10,2) NOT NULL DEFAULT 0.00,
  `amount_received` decimal(10,2) NOT NULL DEFAULT 0.00,
  `refund_amount` decimal(10,2) NOT NULL DEFAULT 0.00,
  `payment_status` enum('unpaid','paid','exempt','not_required','refunded') NOT NULL DEFAULT 'not_required',
  `payment_method` varchar(50) DEFAULT NULL,
  `payment_reference` varchar(100) DEFAULT NULL,
  `paid_at` datetime DEFAULT NULL,
  `refunded_at` datetime DEFAULT NULL,
  `treasurer_notes` text DEFAULT NULL,
  `payment_updated_by` bigint(20) unsigned DEFAULT NULL,
  `arrived` tinyint(1) NOT NULL DEFAULT 0,
  `arrived_at` datetime DEFAULT NULL,
  `checked_in_by` bigint(20) unsigned DEFAULT NULL,
  `confirmation_sent_at` datetime DEFAULT NULL,
  `calendar_sent_at` datetime DEFAULT NULL,
  `test_booking` tinyint(1) NOT NULL DEFAULT 0,
  `cancelled_at` datetime DEFAULT NULL,
  `offer_expires_at` datetime DEFAULT NULL,
  `anonymised_at` datetime DEFAULT NULL,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  `updated_at` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_booking_reference` (`booking_reference`),
  KEY `idx_booking_meeting` (`meeting_id`),
  KEY `idx_booking_email` (`email`),
  KEY `idx_booking_status` (`meeting_id`,`status`),
  KEY `idx_booking_payment` (`meeting_id`,`payment_status`),
  KEY `fk_payment_updated_by` (`payment_updated_by`),
  KEY `fk_booking_checked_in_by` (`checked_in_by`),
  KEY `idx_booking_code_lookup` (`booking_code_lookup`),
  CONSTRAINT `fk_booking_checked_in_by` FOREIGN KEY (`checked_in_by`) REFERENCES `users` (`id`) ON DELETE SET NULL,
  CONSTRAINT `fk_booking_meeting` FOREIGN KEY (`meeting_id`) REFERENCES `meetings` (`id`),
  CONSTRAINT `fk_payment_updated_by` FOREIGN KEY (`payment_updated_by`) REFERENCES `users` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `candidate_admissions` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `candidate_id` bigint(20) unsigned NOT NULL,
  `member_id` bigint(20) unsigned NOT NULL,
  `ceremony_date` date NOT NULL,
  `terminology` varchar(80) NOT NULL,
  `confirmed_by` bigint(20) unsigned NOT NULL,
  `confirmed_at` datetime NOT NULL DEFAULT current_timestamp(),
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_candidate_admission` (`candidate_id`),
  UNIQUE KEY `uq_admission_member` (`member_id`),
  KEY `fk_admission_actor` (`confirmed_by`),
  CONSTRAINT `fk_admission_actor` FOREIGN KEY (`confirmed_by`) REFERENCES `users` (`id`),
  CONSTRAINT `fk_admission_candidate` FOREIGN KEY (`candidate_id`) REFERENCES `unit_candidates` (`id`),
  CONSTRAINT `fk_admission_member` FOREIGN KEY (`member_id`) REFERENCES `unit_members` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `candidate_history` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `candidate_id` bigint(20) unsigned NOT NULL,
  `actor_id` bigint(20) unsigned NOT NULL,
  `action_key` varchar(60) NOT NULL,
  `from_stage` varchar(30) DEFAULT NULL,
  `to_stage` varchar(30) DEFAULT NULL,
  `occurred_at` datetime NOT NULL DEFAULT current_timestamp(),
  PRIMARY KEY (`id`),
  KEY `idx_candidate_history` (`candidate_id`,`occurred_at`),
  KEY `fk_candidate_history_actor` (`actor_id`),
  CONSTRAINT `fk_candidate_history_actor` FOREIGN KEY (`actor_id`) REFERENCES `users` (`id`),
  CONSTRAINT `fk_candidate_history_candidate` FOREIGN KEY (`candidate_id`) REFERENCES `unit_candidates` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `communication_log` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `unit_id` bigint(20) unsigned NOT NULL,
  `meeting_id` bigint(20) unsigned DEFAULT NULL,
  `booking_id` bigint(20) unsigned DEFAULT NULL,
  `template_key` varchar(80) DEFAULT NULL,
  `recipient` varchar(254) NOT NULL,
  `subject` varchar(250) NOT NULL,
  `body_html` mediumtext DEFAULT NULL,
  `status` enum('queued','sent','failed') NOT NULL DEFAULT 'queued',
  `error_message` text DEFAULT NULL,
  `retry_count` int(10) unsigned NOT NULL DEFAULT 0,
  `last_attempt_at` datetime DEFAULT NULL,
  `sent_by` bigint(20) unsigned DEFAULT NULL,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  `sent_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_communication_meeting` (`meeting_id`,`created_at`),
  KEY `fk_communication_booking` (`booking_id`),
  KEY `fk_communication_user` (`sent_by`),
  KEY `idx_communication_log_unit` (`unit_id`),
  CONSTRAINT `fk_communication_booking` FOREIGN KEY (`booking_id`) REFERENCES `bookings` (`id`) ON DELETE SET NULL,
  CONSTRAINT `fk_communication_log_unit` FOREIGN KEY (`unit_id`) REFERENCES `units` (`id`),
  CONSTRAINT `fk_communication_meeting` FOREIGN KEY (`meeting_id`) REFERENCES `meetings` (`id`) ON DELETE SET NULL,
  CONSTRAINT `fk_communication_user` FOREIGN KEY (`sent_by`) REFERENCES `users` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `communication_suppressions` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `unit_id` bigint(20) unsigned NOT NULL,
  `contact_hash` char(64) NOT NULL,
  `channel` enum('email','phone','post','all') NOT NULL DEFAULT 'email',
  `reason` enum('objection','consent_withdrawal','erasure') NOT NULL,
  `source_case_id` bigint(20) unsigned DEFAULT NULL,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_communication_suppression` (`unit_id`,`contact_hash`,`channel`),
  KEY `fk_suppression_case` (`source_case_id`),
  CONSTRAINT `fk_suppression_case` FOREIGN KEY (`source_case_id`) REFERENCES `data_governance_cases` (`id`) ON DELETE SET NULL,
  CONSTRAINT `fk_suppression_unit` FOREIGN KEY (`unit_id`) REFERENCES `units` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `contact_messages` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `unit_id` bigint(20) unsigned NOT NULL,
  `name` varchar(200) NOT NULL,
  `email` varchar(254) NOT NULL,
  `subject` varchar(150) NOT NULL,
  `other_subject` varchar(200) DEFAULT NULL,
  `message` text NOT NULL,
  `recipients` text NOT NULL,
  `status` enum('new','sent','failed','archived') NOT NULL DEFAULT 'new',
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  PRIMARY KEY (`id`),
  KEY `idx_contact_status` (`status`,`created_at`),
  KEY `idx_contact_messages_unit` (`unit_id`),
  CONSTRAINT `fk_contact_messages_unit` FOREIGN KEY (`unit_id`) REFERENCES `units` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `custom_orders` (
  `unit_id` bigint(20) unsigned NOT NULL,
  `order_key` varchar(40) NOT NULL,
  `order_name` varchar(150) NOT NULL,
  `member_noun` varchar(50) NOT NULL DEFAULT 'Member',
  `body_noun` varchar(50) NOT NULL DEFAULT 'Lodge',
  `salutations` text DEFAULT NULL,
  `active` tinyint(1) NOT NULL DEFAULT 1,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  `updated_at` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  PRIMARY KEY (`unit_id`,`order_key`),
  UNIQUE KEY `uq_custom_order_name` (`order_name`),
  CONSTRAINT `fk_custom_orders_unit` FOREIGN KEY (`unit_id`) REFERENCES `units` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `data_governance_case_events` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `case_id` bigint(20) unsigned NOT NULL,
  `actor_id` bigint(20) unsigned NOT NULL,
  `event_key` varchar(80) NOT NULL,
  `restricted_note` text DEFAULT NULL,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  PRIMARY KEY (`id`),
  KEY `idx_governance_event` (`case_id`,`created_at`),
  KEY `fk_governance_event_actor` (`actor_id`),
  CONSTRAINT `fk_governance_event_actor` FOREIGN KEY (`actor_id`) REFERENCES `users` (`id`),
  CONSTRAINT `fk_governance_event_case` FOREIGN KEY (`case_id`) REFERENCES `data_governance_cases` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `data_governance_case_people` (
  `case_id` bigint(20) unsigned NOT NULL,
  `person_id` bigint(20) unsigned NOT NULL,
  `link_status` enum('possible','verified','excluded') NOT NULL DEFAULT 'possible',
  `reviewed_by` bigint(20) unsigned DEFAULT NULL,
  `reviewed_at` datetime DEFAULT NULL,
  PRIMARY KEY (`case_id`,`person_id`),
  KEY `fk_governance_person_person` (`person_id`),
  KEY `fk_governance_person_reviewer` (`reviewed_by`),
  CONSTRAINT `fk_governance_person_case` FOREIGN KEY (`case_id`) REFERENCES `data_governance_cases` (`id`) ON DELETE CASCADE,
  CONSTRAINT `fk_governance_person_person` FOREIGN KEY (`person_id`) REFERENCES `people` (`id`),
  CONSTRAINT `fk_governance_person_reviewer` FOREIGN KEY (`reviewed_by`) REFERENCES `users` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `data_governance_cases` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `unit_id` bigint(20) unsigned NOT NULL,
  `request_type` enum('access','correction','consent_withdrawal','restriction','erasure') NOT NULL,
  `request_received_at` datetime NOT NULL,
  `requester_name` varchar(200) NOT NULL,
  `requester_contact` varchar(254) DEFAULT NULL,
  `identity_status` enum('unverified','verified','failed') NOT NULL DEFAULT 'unverified',
  `identity_verified_at` datetime DEFAULT NULL,
  `identity_verified_by` bigint(20) unsigned DEFAULT NULL,
  `identity_method` varchar(80) DEFAULT NULL,
  `scope_summary` varchar(500) NOT NULL,
  `responsible_officer_id` bigint(20) unsigned DEFAULT NULL,
  `deadline_at` datetime NOT NULL,
  `status` enum('received','identity_check','review','restricted','decided','completed','rejected') NOT NULL DEFAULT 'received',
  `decision_code` enum('pending','approved','partially_approved','refused','retained_obligation','withdrawal_recorded') NOT NULL DEFAULT 'pending',
  `decision_summary` text DEFAULT NULL,
  `completed_at` datetime DEFAULT NULL,
  `completed_by` bigint(20) unsigned DEFAULT NULL,
  `created_by` bigint(20) unsigned NOT NULL,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  `updated_at` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  PRIMARY KEY (`id`),
  KEY `idx_governance_case_queue` (`unit_id`,`status`,`deadline_at`),
  KEY `idx_governance_handler` (`responsible_officer_id`,`status`),
  KEY `fk_governance_case_verifier` (`identity_verified_by`),
  KEY `fk_governance_case_completer` (`completed_by`),
  KEY `fk_governance_case_creator` (`created_by`),
  CONSTRAINT `fk_governance_case_completer` FOREIGN KEY (`completed_by`) REFERENCES `users` (`id`),
  CONSTRAINT `fk_governance_case_creator` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`),
  CONSTRAINT `fk_governance_case_handler` FOREIGN KEY (`responsible_officer_id`) REFERENCES `users` (`id`),
  CONSTRAINT `fk_governance_case_unit` FOREIGN KEY (`unit_id`) REFERENCES `units` (`id`),
  CONSTRAINT `fk_governance_case_verifier` FOREIGN KEY (`identity_verified_by`) REFERENCES `users` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `data_governance_exports` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `case_id` bigint(20) unsigned NOT NULL,
  `token_hash` char(64) NOT NULL,
  `payload_cipher` longtext NOT NULL,
  `expires_at` datetime NOT NULL,
  `created_by` bigint(20) unsigned NOT NULL,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  `downloaded_at` datetime DEFAULT NULL,
  `downloaded_by` bigint(20) unsigned DEFAULT NULL,
  `revoked_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_governance_export_token` (`token_hash`),
  KEY `idx_governance_export_case` (`case_id`,`expires_at`),
  KEY `fk_governance_export_creator` (`created_by`),
  KEY `fk_governance_export_downloader` (`downloaded_by`),
  CONSTRAINT `fk_governance_export_case` FOREIGN KEY (`case_id`) REFERENCES `data_governance_cases` (`id`) ON DELETE CASCADE,
  CONSTRAINT `fk_governance_export_creator` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`),
  CONSTRAINT `fk_governance_export_downloader` FOREIGN KEY (`downloaded_by`) REFERENCES `users` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `data_governance_source_reviews` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `case_id` bigint(20) unsigned NOT NULL,
  `data_class` enum('person','membership','candidate','contact','booking','guest','message','document','welfare') NOT NULL,
  `source_id` bigint(20) unsigned NOT NULL,
  `subject_person_id` bigint(20) unsigned DEFAULT NULL,
  `review_status` enum('pending','include','exclude','redact') NOT NULL DEFAULT 'pending',
  `third_party_status` enum('not_applicable','pending','cleared','redacted','excluded') NOT NULL DEFAULT 'pending',
  `restricted_note` text DEFAULT NULL,
  `reviewed_by` bigint(20) unsigned DEFAULT NULL,
  `reviewed_at` datetime DEFAULT NULL,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_governance_source` (`case_id`,`data_class`,`source_id`),
  KEY `idx_governance_source_review` (`case_id`,`review_status`,`third_party_status`),
  KEY `fk_governance_source_person` (`subject_person_id`),
  KEY `fk_governance_source_reviewer` (`reviewed_by`),
  CONSTRAINT `fk_governance_source_case` FOREIGN KEY (`case_id`) REFERENCES `data_governance_cases` (`id`) ON DELETE CASCADE,
  CONSTRAINT `fk_governance_source_person` FOREIGN KEY (`subject_person_id`) REFERENCES `people` (`id`),
  CONSTRAINT `fk_governance_source_reviewer` FOREIGN KEY (`reviewed_by`) REFERENCES `users` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `dining_tables` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `meeting_id` bigint(20) unsigned NOT NULL,
  `table_name` varchar(100) NOT NULL,
  `capacity` int(10) unsigned NOT NULL DEFAULT 8,
  `display_order` int(10) unsigned NOT NULL DEFAULT 0,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_dining_table` (`meeting_id`,`table_name`),
  CONSTRAINT `fk_table_meeting` FOREIGN KEY (`meeting_id`) REFERENCES `meetings` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `documents` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `unit_id` bigint(20) unsigned NOT NULL,
  `title` varchar(200) NOT NULL,
  `description` varchar(500) DEFAULT NULL,
  `document_url` text NOT NULL,
  `category` enum('summons','minutes','bylaws','constitutions','forms_guidance','other') NOT NULL DEFAULT 'other',
  `visibility` enum('public','members','officers','administrators') NOT NULL DEFAULT 'members',
  `active` tinyint(1) NOT NULL DEFAULT 1,
  `display_order` int(10) unsigned NOT NULL DEFAULT 0,
  `created_by` bigint(20) unsigned DEFAULT NULL,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  `updated_at` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  PRIMARY KEY (`id`),
  KEY `idx_documents_category_visibility` (`category`,`visibility`,`active`,`display_order`),
  KEY `fk_document_user` (`created_by`),
  KEY `idx_documents_unit` (`unit_id`),
  CONSTRAINT `fk_document_user` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`) ON DELETE SET NULL,
  CONSTRAINT `fk_documents_unit` FOREIGN KEY (`unit_id`) REFERENCES `units` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `email_templates` (
  `unit_id` bigint(20) unsigned NOT NULL,
  `template_key` varchar(80) NOT NULL,
  `display_name` varchar(150) NOT NULL,
  `subject_template` varchar(250) NOT NULL,
  `body_template` mediumtext NOT NULL,
  `enabled` tinyint(1) NOT NULL DEFAULT 1,
  `updated_by` bigint(20) unsigned DEFAULT NULL,
  `updated_at` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  PRIMARY KEY (`unit_id`,`template_key`),
  KEY `fk_email_template_user` (`updated_by`),
  CONSTRAINT `fk_email_template_user` FOREIGN KEY (`updated_by`) REFERENCES `users` (`id`) ON DELETE SET NULL,
  CONSTRAINT `fk_email_templates_unit` FOREIGN KEY (`unit_id`) REFERENCES `units` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `event_programmes` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `unit_id` bigint(20) unsigned NOT NULL,
  `programme_key` varchar(140) NOT NULL,
  `title` varchar(200) NOT NULL,
  `capacity` int(10) unsigned NOT NULL,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  `updated_at` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_programme_unit_key` (`unit_id`,`programme_key`),
  UNIQUE KEY `uq_programme_id_unit` (`id`,`unit_id`),
  CONSTRAINT `fk_programme_unit` FOREIGN KEY (`unit_id`) REFERENCES `units` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `guest_contacts` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `unit_id` bigint(20) unsigned NOT NULL,
  `sponsor_email` varchar(254) NOT NULL,
  `salutation` varchar(30) NOT NULL,
  `first_name` varchar(100) NOT NULL,
  `surname` varchar(100) NOT NULL,
  `email` varchar(254) NOT NULL,
  `masonic_rank` varchar(150) DEFAULT NULL,
  `provincial_rank` varchar(150) DEFAULT NULL,
  `grand_rank` varchar(150) DEFAULT NULL,
  `lodge_name` varchar(150) DEFAULT NULL,
  `lodge_number` varchar(30) DEFAULT NULL,
  `dietary_requirements` text DEFAULT NULL,
  `allergy_details` text DEFAULT NULL,
  `consented_at` datetime NOT NULL,
  `consent_text` text DEFAULT NULL,
  `consent_version` varchar(40) DEFAULT NULL,
  `source_booking_id` bigint(20) unsigned DEFAULT NULL,
  `retention_expires_at` datetime DEFAULT NULL,
  `last_booked_at` datetime NOT NULL,
  `consent_withdrawn_at` datetime DEFAULT NULL,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  `updated_at` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_guest_contact_unit_sponsor` (`unit_id`,`sponsor_email`,`email`),
  KEY `idx_guest_contact_email` (`email`),
  KEY `idx_guest_contact_consent` (`consent_withdrawn_at`,`last_booked_at`),
  KEY `idx_guest_contacts_unit` (`unit_id`),
  KEY `idx_guest_contact_expiry` (`unit_id`,`retention_expires_at`),
  KEY `idx_guest_contact_source` (`source_booking_id`),
  CONSTRAINT `fk_guest_contact_source_booking` FOREIGN KEY (`source_booking_id`) REFERENCES `bookings` (`id`) ON DELETE SET NULL,
  CONSTRAINT `fk_guest_contacts_unit` FOREIGN KEY (`unit_id`) REFERENCES `units` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `guest_directory_rate_limits` (
  `unit_id` bigint(20) unsigned NOT NULL,
  `bucket` char(64) NOT NULL,
  `attempts` smallint(5) unsigned NOT NULL DEFAULT 0,
  `window_started` datetime NOT NULL,
  PRIMARY KEY (`unit_id`,`bucket`),
  CONSTRAINT `fk_guest_directory_rate_unit` FOREIGN KEY (`unit_id`) REFERENCES `units` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `guest_invitations` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `guest_contact_id` bigint(20) unsigned NOT NULL,
  `meeting_id` bigint(20) unsigned NOT NULL,
  `status` enum('queued','sent','failed') NOT NULL,
  `sent_at` datetime DEFAULT NULL,
  `failure_message` text DEFAULT NULL,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  `updated_at` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_guest_meeting_invitation` (`guest_contact_id`,`meeting_id`),
  KEY `fk_guest_invitation_meeting` (`meeting_id`),
  CONSTRAINT `fk_guest_invitation_contact` FOREIGN KEY (`guest_contact_id`) REFERENCES `guest_contacts` (`id`) ON DELETE CASCADE,
  CONSTRAINT `fk_guest_invitation_meeting` FOREIGN KEY (`meeting_id`) REFERENCES `meetings` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `guests` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `booking_id` bigint(20) unsigned NOT NULL,
  `salutation` varchar(30) NOT NULL,
  `first_name` varchar(100) NOT NULL,
  `surname` varchar(100) NOT NULL,
  `email` varchar(254) DEFAULT NULL,
  `retention_consent` tinyint(1) NOT NULL DEFAULT 0,
  `guest_contact_id` bigint(20) unsigned DEFAULT NULL,
  `masonic_rank` varchar(150) DEFAULT NULL,
  `provincial_rank` varchar(150) DEFAULT NULL,
  `grand_rank` varchar(150) DEFAULT NULL,
  `lodge_name` varchar(150) DEFAULT NULL,
  `lodge_number` varchar(30) DEFAULT NULL,
  `dining` tinyint(1) NOT NULL DEFAULT 0,
  `dietary_requirements` text DEFAULT NULL,
  `allergy_details` text DEFAULT NULL,
  `dining_charge` decimal(10,2) NOT NULL DEFAULT 0.00,
  `arrived` tinyint(1) NOT NULL DEFAULT 0,
  `arrived_at` datetime DEFAULT NULL,
  `checked_in_by` bigint(20) unsigned DEFAULT NULL,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  `updated_at` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  PRIMARY KEY (`id`),
  KEY `idx_guest_booking` (`booking_id`),
  KEY `idx_guest_email` (`email`),
  KEY `idx_guest_contact` (`guest_contact_id`),
  KEY `idx_guest_arrival` (`arrived`,`arrived_at`),
  KEY `fk_guest_checked_in_by` (`checked_in_by`),
  CONSTRAINT `fk_guest_booking` FOREIGN KEY (`booking_id`) REFERENCES `bookings` (`id`) ON DELETE CASCADE,
  CONSTRAINT `fk_guest_checked_in_by` FOREIGN KEY (`checked_in_by`) REFERENCES `users` (`id`) ON DELETE SET NULL,
  CONSTRAINT `fk_guest_contact` FOREIGN KEY (`guest_contact_id`) REFERENCES `guest_contacts` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `historical_migration_reconciliation` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `filename` varchar(255) NOT NULL,
  `checksum` char(64) NOT NULL,
  `status` enum('present_unverified','reconciled','not_applied') NOT NULL DEFAULT 'present_unverified',
  `notes` text DEFAULT NULL,
  `reconciled_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_historical_filename` (`filename`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `import_conflicts` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `import_run_id` bigint(20) unsigned NOT NULL,
  `source_table` varchar(100) NOT NULL,
  `source_id` varchar(191) NOT NULL,
  `conflict_type` varchar(100) NOT NULL,
  `details_json` longtext DEFAULT NULL,
  `resolution` varchar(100) DEFAULT NULL,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  PRIMARY KEY (`id`),
  KEY `fk_import_conflict_run` (`import_run_id`),
  CONSTRAINT `fk_import_conflict_run` FOREIGN KEY (`import_run_id`) REFERENCES `import_runs` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `import_id_map` (
  `source_installation_id` bigint(20) unsigned NOT NULL,
  `source_table` varchar(100) NOT NULL,
  `source_id` varchar(191) NOT NULL,
  `destination_table` varchar(100) NOT NULL,
  `destination_id` varchar(191) NOT NULL,
  `natural_key_hash` char(64) DEFAULT NULL,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  PRIMARY KEY (`source_installation_id`,`source_table`,`source_id`),
  KEY `idx_import_destination` (`destination_table`,`destination_id`),
  CONSTRAINT `fk_import_map_source` FOREIGN KEY (`source_installation_id`) REFERENCES `source_installations` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `import_runs` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `source_installation_id` bigint(20) unsigned NOT NULL,
  `dry_run` tinyint(1) NOT NULL DEFAULT 1,
  `status` enum('running','complete','failed') NOT NULL DEFAULT 'running',
  `started_at` datetime NOT NULL DEFAULT current_timestamp(),
  `completed_at` datetime DEFAULT NULL,
  `report_json` longtext DEFAULT NULL,
  `error_message` text DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `fk_import_run_source` (`source_installation_id`),
  CONSTRAINT `fk_import_run_source` FOREIGN KEY (`source_installation_id`) REFERENCES `source_installations` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `kiosk_sessions` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `token_hash` char(64) NOT NULL,
  `user_id` bigint(20) unsigned NOT NULL,
  `unit_id` bigint(20) unsigned NOT NULL,
  `expires_at` datetime NOT NULL,
  `revoked_at` datetime DEFAULT NULL,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  PRIMARY KEY (`id`),
  UNIQUE KEY `token_hash` (`token_hash`),
  KEY `idx_kiosk_expiry` (`expires_at`),
  KEY `user_id` (`user_id`),
  KEY `unit_id` (`unit_id`),
  CONSTRAINT `kiosk_sessions_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE,
  CONSTRAINT `kiosk_sessions_ibfk_2` FOREIGN KEY (`unit_id`) REFERENCES `units` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `lodge_settings` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `unit_id` bigint(20) unsigned NOT NULL,
  `lodge_name` varchar(150) NOT NULL,
  `lodge_number` varchar(30) NOT NULL,
  `lodge_short_name` varchar(100) DEFAULT NULL,
  `order_name` varchar(150) DEFAULT NULL,
  `province_name` varchar(200) DEFAULT NULL,
  `lodge_logo_url` text DEFAULT NULL,
  `lodge_honours_logo_url` text DEFAULT NULL,
  `province_logo_url` text DEFAULT NULL,
  `order_logo_url` text DEFAULT NULL,
  `companion_order_logo_url` text DEFAULT NULL,
  `charity_logo_url` text DEFAULT NULL,
  `primary_colour` varchar(7) NOT NULL DEFAULT '#990033',
  `secondary_colour` varchar(7) NOT NULL DEFAULT '#122849',
  `accent_colour` varchar(7) NOT NULL DEFAULT '#C7A646',
  `background_colour` varchar(7) NOT NULL DEFAULT '#F7F1E3',
  `panel_colour` varchar(7) NOT NULL DEFAULT '#FFFDFA',
  `text_colour` varchar(7) NOT NULL DEFAULT '#27231E',
  `heading_font` varchar(120) NOT NULL DEFAULT 'Georgia, serif',
  `body_font` varchar(120) NOT NULL DEFAULT 'system-ui, sans-serif',
  `browser_title` varchar(200) DEFAULT NULL,
  `favicon_url` text DEFAULT NULL,
  `secretary_email` varchar(254) DEFAULT NULL,
  `treasurer_email` varchar(254) DEFAULT NULL,
  `dining_steward_email` varchar(254) DEFAULT NULL,
  `director_of_ceremonies_email` varchar(254) DEFAULT NULL,
  `almoner_email` varchar(254) DEFAULT NULL,
  `general_email` varchar(254) DEFAULT NULL,
  `legal_contact_email` varchar(254) DEFAULT NULL,
  `email_display_name` varchar(150) DEFAULT NULL,
  `provincial_website_url` text DEFAULT NULL,
  `provincial_website_label` varchar(150) DEFAULT NULL,
  `order_website_url` text DEFAULT NULL,
  `order_website_label` varchar(150) DEFAULT NULL,
  `book_of_constitutions_url` text DEFAULT NULL,
  `bylaws_url` text DEFAULT NULL,
  `timezone` varchar(80) NOT NULL DEFAULT 'Europe/London',
  `currency_code` char(3) NOT NULL DEFAULT 'GBP',
  `booking_reference_prefix` varchar(30) DEFAULT NULL,
  `retention_meetings` int(10) unsigned NOT NULL DEFAULT 4,
  `retention_months` int(10) unsigned NOT NULL DEFAULT 18,
  `automatic_backup` tinyint(1) NOT NULL DEFAULT 1,
  `backup_retention_days` int(10) unsigned NOT NULL DEFAULT 30,
  `health_alert_email` varchar(254) DEFAULT NULL,
  `bank_account_name` varchar(150) DEFAULT NULL,
  `bank_sort_code` varchar(20) DEFAULT NULL,
  `bank_account_number` varchar(40) DEFAULT NULL,
  `updated_by` bigint(20) unsigned DEFAULT NULL,
  `updated_at` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_lodge_settings_unit` (`unit_id`),
  KEY `fk_lodge_settings_user` (`updated_by`),
  KEY `idx_lodge_settings_unit` (`unit_id`),
  CONSTRAINT `fk_lodge_settings_unit` FOREIGN KEY (`unit_id`) REFERENCES `units` (`id`),
  CONSTRAINT `fk_lodge_settings_user` FOREIGN KEY (`updated_by`) REFERENCES `users` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `login_challenges` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `user_id` bigint(20) unsigned NOT NULL,
  `code_hash` varchar(255) NOT NULL,
  `expires_at` datetime NOT NULL,
  `attempts` int(10) unsigned NOT NULL DEFAULT 0,
  `used_at` datetime DEFAULT NULL,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  PRIMARY KEY (`id`),
  KEY `idx_login_challenge_user` (`user_id`,`expires_at`),
  CONSTRAINT `fk_login_challenge_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `meeting_menu` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `meeting_id` bigint(20) unsigned NOT NULL,
  `course_name` varchar(100) NOT NULL,
  `dish` text NOT NULL,
  `vegetarian_alternative` text DEFAULT NULL,
  `vegan_alternative` text DEFAULT NULL,
  `pescetarian_alternative` text DEFAULT NULL,
  `kosher_alternative` text DEFAULT NULL,
  `halal_alternative` text DEFAULT NULL,
  `hindu_alternative` text DEFAULT NULL,
  `sikh_alternative` text DEFAULT NULL,
  `display_order` int(10) unsigned NOT NULL DEFAULT 0,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  PRIMARY KEY (`id`),
  KEY `idx_menu_meeting` (`meeting_id`,`display_order`),
  CONSTRAINT `fk_menu_meeting` FOREIGN KEY (`meeting_id`) REFERENCES `meetings` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `meetings` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `unit_id` bigint(20) unsigned NOT NULL,
  `programme_id` bigint(20) unsigned DEFAULT NULL,
  `event_slug` varchar(120) NOT NULL,
  `event_type` varchar(30) NOT NULL DEFAULT 'regular',
  `lodge_name` varchar(150) NOT NULL DEFAULT 'Your Lodge',
  `lodge_number` varchar(20) NOT NULL DEFAULT '0',
  `masonic_order` varchar(40) NOT NULL DEFAULT 'craft',
  `linked_event_group` varchar(100) DEFAULT NULL,
  `title` varchar(200) NOT NULL,
  `meeting_date` date NOT NULL,
  `meeting_start` datetime NOT NULL,
  `meeting_end` datetime DEFAULT NULL,
  `booking_deadline` datetime NOT NULL,
  `venue_name` varchar(200) NOT NULL,
  `venue_address` text DEFAULT NULL,
  `dining_capacity` int(10) unsigned NOT NULL DEFAULT 80,
  `member_price` decimal(10,2) NOT NULL DEFAULT 0.00,
  `visitor_price` decimal(10,2) NOT NULL DEFAULT 0.00,
  `special_event_enabled` tinyint(1) NOT NULL DEFAULT 0,
  `special_event_official_visits` tinyint(1) NOT NULL DEFAULT 0,
  `special_event_type` enum('none','ladies_evening','white_table','blue_table','other') NOT NULL DEFAULT 'none',
  `special_event_label` varchar(120) DEFAULT NULL,
  `special_event_description` text DEFAULT NULL,
  `raffle_enabled` tinyint(1) NOT NULL DEFAULT 0,
  `raffle_sales_unit` enum('ticket','strip') NOT NULL DEFAULT 'ticket',
  `raffle_ticket_price` decimal(10,2) NOT NULL DEFAULT 0.00,
  `raffle_max_per_booking` int(10) unsigned DEFAULT NULL,
  `raffle_description` varchar(500) DEFAULT NULL,
  `template_source_id` bigint(20) unsigned DEFAULT NULL,
  `summons_url` text DEFAULT NULL,
  `document_kind` varchar(12) NOT NULL DEFAULT 'summons',
  `toast_list_url` text DEFAULT NULL,
  `alms_collection_url` text DEFAULT NULL,
  `menu_note` text DEFAULT NULL,
  `bookings_open` tinyint(1) NOT NULL DEFAULT 1,
  `calendar_invite_enabled` tinyint(1) NOT NULL DEFAULT 0,
  `lifecycle_status` enum('draft','open','closed','completed','archived','cancelled') NOT NULL DEFAULT 'open',
  `test_mode` tinyint(1) NOT NULL DEFAULT 0,
  `archived` tinyint(1) NOT NULL DEFAULT 0,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  `updated_at` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_meeting_unit_slug` (`unit_id`,`event_slug`),
  KEY `idx_meeting_date` (`meeting_date`),
  KEY `idx_meeting_order` (`masonic_order`),
  KEY `idx_meeting_linked_group` (`linked_event_group`),
  KEY `idx_meeting_status` (`archived`,`bookings_open`),
  KEY `idx_meeting_lifecycle` (`lifecycle_status`,`meeting_date`),
  KEY `idx_meeting_public_booking` (`archived`,`bookings_open`,`lifecycle_status`,`booking_deadline`,`meeting_date`),
  KEY `idx_meeting_unit_public` (`unit_id`,`archived`,`bookings_open`,`lifecycle_status`,`booking_deadline`),
  KEY `idx_meeting_programme` (`programme_id`,`unit_id`),
  CONSTRAINT `fk_meeting_programme_unit` FOREIGN KEY (`programme_id`, `unit_id`) REFERENCES `event_programmes` (`id`, `unit_id`),
  CONSTRAINT `fk_meeting_unit` FOREIGN KEY (`unit_id`) REFERENCES `units` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `member_milestones` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `member_id` bigint(20) unsigned NOT NULL,
  `milestone_key` varchar(60) NOT NULL,
  `terminology` varchar(80) NOT NULL,
  `ceremony_date` date NOT NULL,
  `confirmed_by` bigint(20) unsigned NOT NULL,
  `confirmed_at` datetime NOT NULL DEFAULT current_timestamp(),
  `updated_at` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_member_milestone` (`member_id`,`milestone_key`),
  KEY `fk_milestone_actor` (`confirmed_by`),
  CONSTRAINT `fk_milestone_actor` FOREIGN KEY (`confirmed_by`) REFERENCES `users` (`id`),
  CONSTRAINT `fk_milestone_member` FOREIGN KEY (`member_id`) REFERENCES `unit_members` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `member_offices` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `member_id` bigint(20) unsigned NOT NULL,
  `office_name` varchar(150) NOT NULL,
  `start_date` date NOT NULL,
  `end_date` date DEFAULT NULL,
  `updated_at` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  PRIMARY KEY (`id`),
  KEY `idx_member_office` (`member_id`,`start_date`),
  CONSTRAINT `fk_member_office_member` FOREIGN KEY (`member_id`) REFERENCES `unit_members` (`id`) ON DELETE CASCADE,
  CONSTRAINT `chk_office_dates` CHECK (`end_date` is null or `end_date` >= `start_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `member_ranks` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `member_id` bigint(20) unsigned NOT NULL,
  `rank_scope` enum('masonic','provincial','grand') NOT NULL,
  `rank_name` varchar(150) NOT NULL,
  `attained_date` date DEFAULT NULL,
  `ended_date` date DEFAULT NULL,
  `updated_at` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_member_rank` (`member_id`,`rank_scope`,`rank_name`),
  CONSTRAINT `fk_member_rank_member` FOREIGN KEY (`member_id`) REFERENCES `unit_members` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `migration_history` (
  `version` varchar(12) NOT NULL,
  `name` varchar(200) NOT NULL,
  `checksum` char(64) NOT NULL,
  `applied_at` datetime NOT NULL DEFAULT current_timestamp(),
  `execution_ms` int(10) unsigned NOT NULL DEFAULT 0,
  PRIMARY KEY (`version`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `order_rank_settings` (
  `unit_id` bigint(20) unsigned NOT NULL,
  `order_key` varchar(40) NOT NULL,
  `salutations` text DEFAULT NULL,
  `salutation_rank_links` longtext DEFAULT NULL,
  `masonic_ranks` text DEFAULT NULL,
  `active_provincial_ranks` text DEFAULT NULL,
  `past_provincial_ranks` text DEFAULT NULL,
  `active_grand_ranks` text DEFAULT NULL,
  `past_grand_ranks` text DEFAULT NULL,
  `updated_at` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  PRIMARY KEY (`unit_id`,`order_key`),
  CONSTRAINT `fk_order_rank_settings_unit` FOREIGN KEY (`unit_id`) REFERENCES `units` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `outbound_mail_queue` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `unit_id` bigint(20) unsigned DEFAULT NULL,
  `communication_id` bigint(20) unsigned DEFAULT NULL,
  `booking_id` bigint(20) unsigned DEFAULT NULL,
  `reminder_id` bigint(20) unsigned DEFAULT NULL,
  `contact_id` bigint(20) unsigned DEFAULT NULL,
  `attendee_invitation_id` bigint(20) unsigned DEFAULT NULL,
  `guest_invitation_id` bigint(20) unsigned DEFAULT NULL,
  `recipient` varchar(254) NOT NULL,
  `recipient_key` varchar(254) GENERATED ALWAYS AS (lcase(trim(`recipient`))) STORED,
  `subject` varchar(250) NOT NULL,
  `body_html` mediumtext NOT NULL,
  `attachments_json` mediumtext DEFAULT NULL,
  `message_kind` varchar(80) NOT NULL DEFAULT 'general',
  `status` enum('queued','sending','sent','failed','bounced') NOT NULL DEFAULT 'queued',
  `attempts` tinyint(3) unsigned NOT NULL DEFAULT 0,
  `max_attempts` tinyint(3) unsigned NOT NULL DEFAULT 5,
  `next_attempt_at` datetime NOT NULL DEFAULT current_timestamp(),
  `lease_token` char(32) DEFAULT NULL,
  `locked_until` datetime DEFAULT NULL,
  `sent_at` datetime DEFAULT NULL,
  `last_error_code` varchar(40) DEFAULT NULL,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  `updated_at` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  `offer_booking_id` bigint(20) unsigned DEFAULT NULL,
  `offer_expires_at` datetime DEFAULT NULL,
  `authorising_user_id` bigint(20) unsigned DEFAULT NULL,
  `required_capability` varchar(80) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_mail_ready` (`status`,`next_attempt_at`,`id`),
  KEY `idx_mail_tenant_recipient` (`unit_id`,`recipient_key`,`id`),
  KEY `communication_id` (`communication_id`),
  KEY `booking_id` (`booking_id`),
  KEY `reminder_id` (`reminder_id`),
  KEY `contact_id` (`contact_id`),
  KEY `attendee_invitation_id` (`attendee_invitation_id`),
  KEY `guest_invitation_id` (`guest_invitation_id`),
  KEY `idx_offer_mail` (`offer_booking_id`,`offer_expires_at`,`status`),
  KEY `idx_mail_authority` (`authorising_user_id`,`status`),
  CONSTRAINT `outbound_mail_queue_ibfk_1` FOREIGN KEY (`unit_id`) REFERENCES `units` (`id`) ON DELETE SET NULL,
  CONSTRAINT `outbound_mail_queue_ibfk_2` FOREIGN KEY (`communication_id`) REFERENCES `communication_log` (`id`) ON DELETE SET NULL,
  CONSTRAINT `outbound_mail_queue_ibfk_3` FOREIGN KEY (`booking_id`) REFERENCES `bookings` (`id`) ON DELETE SET NULL,
  CONSTRAINT `outbound_mail_queue_ibfk_4` FOREIGN KEY (`reminder_id`) REFERENCES `reminders` (`id`) ON DELETE SET NULL,
  CONSTRAINT `outbound_mail_queue_ibfk_5` FOREIGN KEY (`contact_id`) REFERENCES `contact_messages` (`id`) ON DELETE SET NULL,
  CONSTRAINT `outbound_mail_queue_ibfk_6` FOREIGN KEY (`attendee_invitation_id`) REFERENCES `attendee_invitations` (`id`) ON DELETE SET NULL,
  CONSTRAINT `outbound_mail_queue_ibfk_7` FOREIGN KEY (`guest_invitation_id`) REFERENCES `guest_invitations` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `password_reset_tokens` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `user_id` bigint(20) unsigned NOT NULL,
  `selector` char(16) NOT NULL,
  `token_hash` char(64) NOT NULL,
  `expires_at` datetime NOT NULL,
  `used_at` datetime DEFAULT NULL,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_password_reset_selector` (`selector`),
  KEY `fk_password_reset_user` (`user_id`),
  CONSTRAINT `fk_password_reset_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `people` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `user_id` bigint(20) unsigned DEFAULT NULL,
  `salutation` varchar(30) DEFAULT NULL,
  `first_name` varchar(100) NOT NULL,
  `surname` varchar(100) NOT NULL,
  `email` varchar(254) DEFAULT NULL,
  `phone` varchar(30) DEFAULT NULL,
  `identity_verified_at` datetime DEFAULT NULL,
  `identity_verified_by` bigint(20) unsigned DEFAULT NULL,
  `legacy_guest_contact_id` bigint(20) unsigned DEFAULT NULL,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  `updated_at` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_people_user` (`user_id`),
  UNIQUE KEY `uq_people_legacy_guest` (`legacy_guest_contact_id`),
  KEY `idx_people_name` (`surname`,`first_name`),
  KEY `idx_people_email` (`email`),
  KEY `fk_people_verifier` (`identity_verified_by`),
  CONSTRAINT `fk_people_guest_contact` FOREIGN KEY (`legacy_guest_contact_id`) REFERENCES `guest_contacts` (`id`) ON DELETE SET NULL,
  CONSTRAINT `fk_people_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL,
  CONSTRAINT `fk_people_verifier` FOREIGN KEY (`identity_verified_by`) REFERENCES `users` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `people_import_actions` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `row_id` bigint(20) unsigned NOT NULL,
  `entity_table` varchar(64) NOT NULL,
  `entity_id` bigint(20) unsigned NOT NULL,
  `expected_updated_at` datetime DEFAULT NULL,
  `undone_at` datetime DEFAULT NULL,
  `undo_note` varchar(191) DEFAULT NULL,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  PRIMARY KEY (`id`),
  KEY `idx_people_import_action_row` (`row_id`,`id`),
  CONSTRAINT `fk_people_import_action_row` FOREIGN KEY (`row_id`) REFERENCES `people_import_rows` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `people_import_batches` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `scope_type` enum('unit','province') NOT NULL,
  `province_id` bigint(20) unsigned NOT NULL,
  `unit_id` bigint(20) unsigned DEFAULT NULL,
  `source_name` varchar(255) NOT NULL,
  `source_sha256` char(64) NOT NULL,
  `source_size` int(10) unsigned NOT NULL,
  `source_system` varchar(100) NOT NULL,
  `mapping_json` longtext NOT NULL,
  `mapping_sha256` char(64) NOT NULL,
  `idempotency_key` char(64) NOT NULL,
  `status` enum('preview','committing','committed','committed_with_errors','failed','undone','undo_partial') NOT NULL DEFAULT 'preview',
  `dry_run` tinyint(1) NOT NULL DEFAULT 1,
  `accepted_count` int(10) unsigned NOT NULL DEFAULT 0,
  `rejected_count` int(10) unsigned NOT NULL DEFAULT 0,
  `unresolved_count` int(10) unsigned NOT NULL DEFAULT 0,
  `applied_count` int(10) unsigned NOT NULL DEFAULT 0,
  `failed_count` int(10) unsigned NOT NULL DEFAULT 0,
  `created_by` bigint(20) unsigned NOT NULL,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  `confirmed_at` datetime DEFAULT NULL,
  `committed_at` datetime DEFAULT NULL,
  `undo_until` datetime DEFAULT NULL,
  `undone_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_people_import_idempotency` (`idempotency_key`),
  KEY `idx_people_import_scope` (`scope_type`,`province_id`,`unit_id`,`status`),
  KEY `fk_people_import_province` (`province_id`),
  KEY `fk_people_import_unit` (`unit_id`),
  KEY `fk_people_import_actor` (`created_by`),
  CONSTRAINT `fk_people_import_actor` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`),
  CONSTRAINT `fk_people_import_province` FOREIGN KEY (`province_id`) REFERENCES `provinces` (`id`),
  CONSTRAINT `fk_people_import_unit` FOREIGN KEY (`unit_id`) REFERENCES `units` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `people_import_duplicate_suggestions` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `row_id` bigint(20) unsigned NOT NULL,
  `person_id` bigint(20) unsigned NOT NULL,
  `score` smallint(5) unsigned NOT NULL,
  `signals_json` longtext NOT NULL,
  `decision` enum('pending','use_existing','create_separate','dismissed') NOT NULL DEFAULT 'pending',
  `reviewed_by` bigint(20) unsigned DEFAULT NULL,
  `reviewed_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_people_import_suggestion` (`row_id`,`person_id`),
  KEY `idx_people_import_suggestion_person` (`person_id`),
  KEY `fk_people_import_suggestion_reviewer` (`reviewed_by`),
  CONSTRAINT `fk_people_import_suggestion_person` FOREIGN KEY (`person_id`) REFERENCES `people` (`id`),
  CONSTRAINT `fk_people_import_suggestion_reviewer` FOREIGN KEY (`reviewed_by`) REFERENCES `users` (`id`),
  CONSTRAINT `fk_people_import_suggestion_row` FOREIGN KEY (`row_id`) REFERENCES `people_import_rows` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `people_import_rows` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `batch_id` bigint(20) unsigned NOT NULL,
  `row_number` int(10) unsigned NOT NULL,
  `source_unit_slug` varchar(191) DEFAULT NULL,
  `unit_id` bigint(20) unsigned DEFAULT NULL,
  `record_type` varchar(32) NOT NULL,
  `row_fingerprint` char(64) NOT NULL,
  `raw_json` longtext NOT NULL,
  `normalized_json` longtext NOT NULL,
  `status` enum('accepted','rejected','unresolved','applied','failed','undone') NOT NULL,
  `validation_errors` longtext DEFAULT NULL,
  `resolved_person_id` bigint(20) unsigned DEFAULT NULL,
  `resolution` enum('new','existing') DEFAULT NULL,
  `applied_at` datetime DEFAULT NULL,
  `failure_code` varchar(80) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_people_import_row` (`batch_id`,`row_number`),
  KEY `idx_people_import_row_status` (`batch_id`,`status`,`row_number`),
  KEY `idx_people_import_row_unit` (`unit_id`),
  KEY `fk_people_import_row_person` (`resolved_person_id`),
  CONSTRAINT `fk_people_import_row_batch` FOREIGN KEY (`batch_id`) REFERENCES `people_import_batches` (`id`) ON DELETE CASCADE,
  CONSTRAINT `fk_people_import_row_person` FOREIGN KEY (`resolved_person_id`) REFERENCES `people` (`id`),
  CONSTRAINT `fk_people_import_row_unit` FOREIGN KEY (`unit_id`) REFERENCES `units` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `permission_grant_reviews` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `grant_id` bigint(20) unsigned NOT NULL,
  `reviewer_id` bigint(20) unsigned NOT NULL,
  `decision` enum('retain','revoke') NOT NULL,
  `previous_expires_at` datetime DEFAULT NULL,
  `expires_at` datetime DEFAULT NULL,
  `next_review_at` datetime DEFAULT NULL,
  `reviewed_at` datetime NOT NULL DEFAULT current_timestamp(),
  PRIMARY KEY (`id`),
  KEY `idx_permission_review_due` (`grant_id`,`next_review_at`),
  KEY `fk_permission_review_actor` (`reviewer_id`),
  CONSTRAINT `fk_permission_review_actor` FOREIGN KEY (`reviewer_id`) REFERENCES `users` (`id`),
  CONSTRAINT `fk_permission_review_grant` FOREIGN KEY (`grant_id`) REFERENCES `scoped_permission_grants` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `permission_set_capabilities` (
  `permission_set_id` bigint(20) unsigned NOT NULL,
  `capability_key` varchar(80) NOT NULL,
  PRIMARY KEY (`permission_set_id`,`capability_key`),
  CONSTRAINT `fk_permission_cap_set` FOREIGN KEY (`permission_set_id`) REFERENCES `permission_sets` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `permission_sets` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(100) NOT NULL,
  `scope_type` enum('platform','province','unit','event') NOT NULL,
  `source_role` varchar(50) DEFAULT NULL,
  `owner_province_id` bigint(20) unsigned DEFAULT NULL,
  `owner_unit_id` bigint(20) unsigned DEFAULT NULL,
  `built_in` tinyint(1) NOT NULL DEFAULT 0,
  `active` tinyint(1) NOT NULL DEFAULT 1,
  `created_by` bigint(20) unsigned DEFAULT NULL,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  `updated_at` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_permission_set_owner_name` (`scope_type`,`owner_province_id`,`owner_unit_id`,`name`),
  KEY `idx_permission_set_owner` (`owner_province_id`,`owner_unit_id`,`active`),
  KEY `fk_permission_set_unit` (`owner_unit_id`),
  KEY `fk_permission_set_creator` (`created_by`),
  CONSTRAINT `fk_permission_set_creator` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`) ON DELETE SET NULL,
  CONSTRAINT `fk_permission_set_province` FOREIGN KEY (`owner_province_id`) REFERENCES `provinces` (`id`) ON DELETE CASCADE,
  CONSTRAINT `fk_permission_set_unit` FOREIGN KEY (`owner_unit_id`) REFERENCES `units` (`id`) ON DELETE CASCADE,
  CONSTRAINT `chk_permission_set_owner` CHECK (`scope_type` = 'platform' and `owner_province_id` is null and `owner_unit_id` is null or `scope_type` = 'province' and `owner_province_id` is not null and `owner_unit_id` is null or `scope_type` in ('unit','event') and `owner_province_id` is null and `owner_unit_id` is not null)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `person_attendance_history` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `unit_id` bigint(20) unsigned NOT NULL,
  `person_id` bigint(20) unsigned NOT NULL,
  `event_date` date NOT NULL,
  `event_title` varchar(255) NOT NULL,
  `attendance_status` enum('attended','apology','absent','unknown') NOT NULL DEFAULT 'unknown',
  `source_row_id` bigint(20) unsigned NOT NULL,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  `updated_at` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_person_attendance_import` (`source_row_id`),
  KEY `idx_person_attendance_unit_date` (`unit_id`,`event_date`),
  KEY `idx_person_attendance_person` (`person_id`),
  CONSTRAINT `fk_person_attendance_person` FOREIGN KEY (`person_id`) REFERENCES `people` (`id`),
  CONSTRAINT `fk_person_attendance_row` FOREIGN KEY (`source_row_id`) REFERENCES `people_import_rows` (`id`),
  CONSTRAINT `fk_person_attendance_unit` FOREIGN KEY (`unit_id`) REFERENCES `units` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `person_duplicate_reviews` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `unit_id` bigint(20) unsigned NOT NULL,
  `person_a_id` bigint(20) unsigned NOT NULL,
  `person_b_id` bigint(20) unsigned NOT NULL,
  `status` enum('pending','same_person','different_people') NOT NULL DEFAULT 'pending',
  `reviewed_by` bigint(20) unsigned DEFAULT NULL,
  `reviewed_at` datetime DEFAULT NULL,
  `canonical_person_id` bigint(20) unsigned DEFAULT NULL,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_duplicate_pair` (`unit_id`,`person_a_id`,`person_b_id`),
  KEY `fk_duplicate_a` (`person_a_id`),
  KEY `fk_duplicate_b` (`person_b_id`),
  KEY `fk_duplicate_reviewer` (`reviewed_by`),
  KEY `fk_duplicate_canonical` (`canonical_person_id`),
  CONSTRAINT `fk_duplicate_a` FOREIGN KEY (`person_a_id`) REFERENCES `people` (`id`) ON DELETE CASCADE,
  CONSTRAINT `fk_duplicate_b` FOREIGN KEY (`person_b_id`) REFERENCES `people` (`id`) ON DELETE CASCADE,
  CONSTRAINT `fk_duplicate_canonical` FOREIGN KEY (`canonical_person_id`) REFERENCES `people` (`id`) ON DELETE SET NULL,
  CONSTRAINT `fk_duplicate_reviewer` FOREIGN KEY (`reviewed_by`) REFERENCES `users` (`id`) ON DELETE SET NULL,
  CONSTRAINT `fk_duplicate_unit` FOREIGN KEY (`unit_id`) REFERENCES `units` (`id`) ON DELETE CASCADE,
  CONSTRAINT `chk_duplicate_order` CHECK (`person_a_id` < `person_b_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `person_import_keys` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `scope_type` enum('unit','province') NOT NULL,
  `scope_id` bigint(20) unsigned NOT NULL,
  `source_system` varchar(100) NOT NULL,
  `external_person_id` varchar(191) NOT NULL,
  `person_id` bigint(20) unsigned NOT NULL,
  `source_row_id` bigint(20) unsigned NOT NULL,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_person_import_key` (`scope_type`,`scope_id`,`source_system`,`external_person_id`),
  KEY `idx_person_import_key_person` (`person_id`),
  KEY `fk_person_import_key_row` (`source_row_id`),
  CONSTRAINT `fk_person_import_key_person` FOREIGN KEY (`person_id`) REFERENCES `people` (`id`),
  CONSTRAINT `fk_person_import_key_row` FOREIGN KEY (`source_row_id`) REFERENCES `people_import_rows` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `platform_email_templates` (
  `template_key` varchar(80) NOT NULL,
  `display_name` varchar(150) NOT NULL,
  `subject_template` varchar(250) NOT NULL,
  `body_template` mediumtext NOT NULL,
  `enabled` tinyint(1) NOT NULL DEFAULT 1,
  `updated_at` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  PRIMARY KEY (`template_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `platform_order_rank_settings` (
  `order_key` varchar(40) NOT NULL,
  `salutations` text DEFAULT NULL,
  `salutation_rank_links` longtext DEFAULT NULL,
  `masonic_ranks` text DEFAULT NULL,
  `active_provincial_ranks` text DEFAULT NULL,
  `past_provincial_ranks` text DEFAULT NULL,
  `active_grand_ranks` text DEFAULT NULL,
  `past_grand_ranks` text DEFAULT NULL,
  `updated_at` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  PRIMARY KEY (`order_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `province_admin_invites` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `province_id` bigint(20) unsigned NOT NULL,
  `role_key` varchar(40) NOT NULL DEFAULT 'province_admin',
  `email` varchar(254) NOT NULL,
  `token_hash` char(64) NOT NULL,
  `invited_by` bigint(20) unsigned DEFAULT NULL,
  `expires_at` datetime NOT NULL,
  `accepted_at` datetime DEFAULT NULL,
  `revoked_at` datetime DEFAULT NULL,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_province_admin_invite_token` (`token_hash`),
  KEY `idx_province_admin_invites` (`province_id`,`created_at`),
  KEY `invited_by` (`invited_by`),
  CONSTRAINT `province_admin_invites_ibfk_1` FOREIGN KEY (`province_id`) REFERENCES `provinces` (`id`) ON DELETE CASCADE,
  CONSTRAINT `province_admin_invites_ibfk_2` FOREIGN KEY (`invited_by`) REFERENCES `users` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `province_audit_log` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `province_id` bigint(20) unsigned NOT NULL,
  `actor_id` bigint(20) unsigned DEFAULT NULL,
  `action` varchar(80) NOT NULL,
  `unit_id` bigint(20) unsigned DEFAULT NULL,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  PRIMARY KEY (`id`),
  KEY `idx_province_audit` (`province_id`,`created_at`,`id`),
  KEY `actor_id` (`actor_id`),
  KEY `unit_id` (`unit_id`),
  CONSTRAINT `province_audit_log_ibfk_1` FOREIGN KEY (`province_id`) REFERENCES `provinces` (`id`) ON DELETE CASCADE,
  CONSTRAINT `province_audit_log_ibfk_2` FOREIGN KEY (`actor_id`) REFERENCES `users` (`id`) ON DELETE SET NULL,
  CONSTRAINT `province_audit_log_ibfk_3` FOREIGN KEY (`unit_id`) REFERENCES `units` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!50003 SET @saved_cs_client      = @@character_set_client */ ;
/*!50003 SET @saved_cs_results     = @@character_set_results */ ;
/*!50003 SET @saved_col_connection = @@collation_connection */ ;
/*!50003 SET character_set_client  = utf8mb4 */ ;
/*!50003 SET character_set_results = utf8mb4 */ ;
/*!50003 SET collation_connection  = utf8mb4_general_ci */ ;
/*!50003 SET @saved_sql_mode       = @@sql_mode */ ;
/*!50003 SET sql_mode              = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ;
DELIMITER ;;
/*!50003 CREATE*/ /*!50003 TRIGGER trg_activity_from_province AFTER INSERT ON province_audit_log FOR EACH ROW
INSERT INTO activity_log(source_kind,source_id,actor_id,province_id,unit_id,action,target_type,target_id,change_summary,occurred_at)
VALUES('province',NEW.id,NEW.actor_id,NEW.province_id,NEW.unit_id,NEW.action,
IF(NEW.unit_id IS NULL,'province','unit'),IFNULL(NEW.unit_id,NEW.province_id),
REPLACE(NEW.action,'_',' '),NEW.created_at) */;;
DELIMITER ;
/*!50003 SET sql_mode              = @saved_sql_mode */ ;
/*!50003 SET character_set_client  = @saved_cs_client */ ;
/*!50003 SET character_set_results = @saved_cs_results */ ;
/*!50003 SET collation_connection  = @saved_col_connection */ ;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `province_email_templates` (
  `province_id` bigint(20) unsigned NOT NULL,
  `template_key` varchar(80) NOT NULL,
  `display_name` varchar(150) NOT NULL,
  `subject_template` varchar(250) NOT NULL,
  `body_template` mediumtext NOT NULL,
  `enabled` tinyint(1) NOT NULL DEFAULT 1,
  `updated_by` bigint(20) unsigned DEFAULT NULL,
  `updated_at` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  PRIMARY KEY (`province_id`,`template_key`),
  KEY `fk_province_email_template_user` (`updated_by`),
  CONSTRAINT `fk_province_email_template_province` FOREIGN KEY (`province_id`) REFERENCES `provinces` (`id`) ON DELETE CASCADE,
  CONSTRAINT `fk_province_email_template_user` FOREIGN KEY (`updated_by`) REFERENCES `users` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `province_event_hosts` (
  `province_id` bigint(20) unsigned NOT NULL,
  `unit_id` bigint(20) unsigned NOT NULL,
  PRIMARY KEY (`province_id`),
  UNIQUE KEY `unit_id` (`unit_id`),
  CONSTRAINT `province_event_hosts_ibfk_1` FOREIGN KEY (`province_id`) REFERENCES `provinces` (`id`) ON DELETE CASCADE,
  CONSTRAINT `province_event_hosts_ibfk_2` FOREIGN KEY (`unit_id`) REFERENCES `units` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `province_events` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `province_id` bigint(20) unsigned NOT NULL,
  `unit_id` bigint(20) unsigned NOT NULL,
  `meeting_id` bigint(20) unsigned NOT NULL,
  `event_kind` enum('grand_lodge','convocation') NOT NULL,
  `created_by` bigint(20) unsigned DEFAULT NULL,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_province_event_meeting` (`meeting_id`),
  KEY `idx_province_events` (`province_id`,`unit_id`),
  KEY `unit_id` (`unit_id`),
  KEY `created_by` (`created_by`),
  CONSTRAINT `province_events_ibfk_1` FOREIGN KEY (`province_id`) REFERENCES `provinces` (`id`) ON DELETE CASCADE,
  CONSTRAINT `province_events_ibfk_2` FOREIGN KEY (`unit_id`) REFERENCES `units` (`id`) ON DELETE CASCADE,
  CONSTRAINT `province_events_ibfk_3` FOREIGN KEY (`meeting_id`) REFERENCES `meetings` (`id`) ON DELETE CASCADE,
  CONSTRAINT `province_events_ibfk_4` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `province_order_rank_settings` (
  `province_id` bigint(20) unsigned NOT NULL,
  `order_key` varchar(40) NOT NULL,
  `salutations` text DEFAULT NULL,
  `salutation_rank_links` longtext DEFAULT NULL,
  `masonic_ranks` text DEFAULT NULL,
  `active_provincial_ranks` text DEFAULT NULL,
  `past_provincial_ranks` text DEFAULT NULL,
  `active_grand_ranks` text DEFAULT NULL,
  `past_grand_ranks` text DEFAULT NULL,
  `updated_at` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  PRIMARY KEY (`province_id`,`order_key`),
  CONSTRAINT `fk_province_rank_province` FOREIGN KEY (`province_id`) REFERENCES `provinces` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `province_settings` (
  `province_id` bigint(20) unsigned NOT NULL,
  `logo_url` varchar(500) DEFAULT NULL,
  `order_logo_url` varchar(500) DEFAULT NULL,
  `charity_logo_url` varchar(500) DEFAULT NULL,
  `primary_colour` char(7) NOT NULL DEFAULT '#990033',
  `secondary_colour` char(7) NOT NULL DEFAULT '#122849',
  `accent_colour` char(7) NOT NULL DEFAULT '#c7a646',
  `contact_name` varchar(150) DEFAULT NULL,
  `contact_email` varchar(254) DEFAULT NULL,
  `contact_phone` varchar(50) DEFAULT NULL,
  `website_url` varchar(500) DEFAULT NULL,
  `postal_address` varchar(500) DEFAULT NULL,
  `updated_by` bigint(20) unsigned DEFAULT NULL,
  `updated_at` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  PRIMARY KEY (`province_id`),
  KEY `updated_by` (`updated_by`),
  CONSTRAINT `province_settings_ibfk_1` FOREIGN KEY (`province_id`) REFERENCES `provinces` (`id`) ON DELETE CASCADE,
  CONSTRAINT `province_settings_ibfk_2` FOREIGN KEY (`updated_by`) REFERENCES `users` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `province_unit_invites` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `province_id` bigint(20) unsigned NOT NULL,
  `unit_id` bigint(20) unsigned NOT NULL,
  `email` varchar(254) NOT NULL,
  `token_hash` char(64) NOT NULL,
  `invited_by` bigint(20) unsigned DEFAULT NULL,
  `expires_at` datetime NOT NULL,
  `accepted_at` datetime DEFAULT NULL,
  `revoked_at` datetime DEFAULT NULL,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_province_invite_token` (`token_hash`),
  KEY `idx_province_invites` (`province_id`,`unit_id`,`created_at`),
  KEY `unit_id` (`unit_id`),
  KEY `invited_by` (`invited_by`),
  CONSTRAINT `province_unit_invites_ibfk_1` FOREIGN KEY (`province_id`) REFERENCES `provinces` (`id`) ON DELETE CASCADE,
  CONSTRAINT `province_unit_invites_ibfk_2` FOREIGN KEY (`unit_id`) REFERENCES `units` (`id`) ON DELETE CASCADE,
  CONSTRAINT `province_unit_invites_ibfk_3` FOREIGN KEY (`invited_by`) REFERENCES `users` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `provinces` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(200) NOT NULL,
  `slug` varchar(100) NOT NULL,
  `order_key` varchar(40) DEFAULT NULL,
  `custom_order_name` varchar(150) DEFAULT NULL,
  `active` tinyint(1) NOT NULL DEFAULT 1,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  `updated_at` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  `normalized_name` varchar(200) GENERATED ALWAYS AS (lcase(trim(`name`))) STORED,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_province_slug` (`slug`),
  UNIQUE KEY `uq_provinces_normalized_name` (`normalized_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `public_event_slug_redirects` (
  `unit_id` bigint(20) unsigned NOT NULL,
  `old_slug` varchar(100) NOT NULL,
  `event_id` bigint(20) unsigned NOT NULL,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  PRIMARY KEY (`unit_id`,`old_slug`),
  KEY `idx_event_slug_redirect_event` (`event_id`),
  CONSTRAINT `fk_public_event_slug_event` FOREIGN KEY (`event_id`) REFERENCES `meetings` (`id`) ON DELETE CASCADE,
  CONSTRAINT `fk_public_event_slug_unit` FOREIGN KEY (`unit_id`) REFERENCES `units` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `reminders` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `booking_id` bigint(20) unsigned NOT NULL,
  `reminder_type` enum('payment','booking_deadline','meeting','waiting_list') NOT NULL,
  `status` enum('pending','sent','failed','cancelled') NOT NULL DEFAULT 'pending',
  `scheduled_for` datetime NOT NULL,
  `sent_at` datetime DEFAULT NULL,
  `failure_message` text DEFAULT NULL,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  `reminder_day` date DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_reminder_booking_type_day` (`booking_id`,`reminder_type`,`reminder_day`),
  KEY `idx_reminder_schedule` (`status`,`scheduled_for`),
  CONSTRAINT `fk_reminder_booking` FOREIGN KEY (`booking_id`) REFERENCES `bookings` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `scheduled_job_runs` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `scope_unit_id` bigint(20) unsigned NOT NULL DEFAULT 0,
  `job_key` varchar(60) NOT NULL,
  `started_at` datetime NOT NULL DEFAULT current_timestamp(),
  `finished_at` datetime DEFAULT NULL,
  `status` enum('running','success','failed') NOT NULL DEFAULT 'running',
  `affected_count` int(10) unsigned NOT NULL DEFAULT 0,
  `summary` varchar(250) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_job_history` (`scope_unit_id`,`job_key`,`started_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `scheduled_job_state` (
  `scope_unit_id` bigint(20) unsigned NOT NULL DEFAULT 0,
  `job_key` varchar(60) NOT NULL,
  `last_started_at` datetime DEFAULT NULL,
  `last_completed_at` datetime DEFAULT NULL,
  `last_success_at` datetime DEFAULT NULL,
  `last_status` enum('never','running','success','failed') NOT NULL DEFAULT 'never',
  `last_summary` varchar(250) DEFAULT NULL,
  PRIMARY KEY (`scope_unit_id`,`job_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `scheduled_payment_reminder_settings` (
  `unit_id` bigint(20) unsigned NOT NULL,
  `enabled` tinyint(1) NOT NULL DEFAULT 0,
  `authorised_by` bigint(20) unsigned DEFAULT NULL,
  `updated_at` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  PRIMARY KEY (`unit_id`),
  KEY `authorised_by` (`authorised_by`),
  CONSTRAINT `scheduled_payment_reminder_settings_ibfk_1` FOREIGN KEY (`unit_id`) REFERENCES `units` (`id`) ON DELETE CASCADE,
  CONSTRAINT `scheduled_payment_reminder_settings_ibfk_2` FOREIGN KEY (`authorised_by`) REFERENCES `users` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `schema_versions` (
  `version_number` int(10) unsigned NOT NULL,
  `version_name` varchar(150) NOT NULL,
  `installed_at` datetime NOT NULL DEFAULT current_timestamp(),
  PRIMARY KEY (`version_number`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `scoped_permission_grants` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `user_id` bigint(20) unsigned NOT NULL,
  `permission_set_id` bigint(20) unsigned NOT NULL,
  `scope_type` enum('platform','province','unit','event') NOT NULL,
  `province_id` bigint(20) unsigned DEFAULT NULL,
  `unit_id` bigint(20) unsigned DEFAULT NULL,
  `event_id` bigint(20) unsigned DEFAULT NULL,
  `active` tinyint(1) NOT NULL DEFAULT 1,
  `expires_at` datetime DEFAULT NULL,
  `revoked_at` datetime DEFAULT NULL,
  `last_used_at` datetime DEFAULT NULL,
  `reviewed_at` datetime DEFAULT NULL,
  `review_due_at` datetime DEFAULT NULL,
  `granted_by` bigint(20) unsigned NOT NULL,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  `scope_key` varchar(100) GENERATED ALWAYS AS (case `scope_type` when 'platform' then 'platform' when 'province' then concat('province:',`province_id`) when 'unit' then concat('unit:',`unit_id`) else concat('event:',`event_id`) end) STORED,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_scoped_permission_grant` (`user_id`,`permission_set_id`,`scope_key`),
  KEY `idx_permission_grant_scope` (`scope_type`,`province_id`,`unit_id`,`event_id`,`active`,`expires_at`),
  KEY `fk_permission_grant_set` (`permission_set_id`),
  KEY `fk_permission_grant_province` (`province_id`),
  KEY `fk_permission_grant_unit` (`unit_id`),
  KEY `fk_permission_grant_event` (`event_id`),
  KEY `fk_permission_grant_actor` (`granted_by`),
  CONSTRAINT `fk_permission_grant_actor` FOREIGN KEY (`granted_by`) REFERENCES `users` (`id`),
  CONSTRAINT `fk_permission_grant_event` FOREIGN KEY (`event_id`) REFERENCES `meetings` (`id`) ON DELETE CASCADE,
  CONSTRAINT `fk_permission_grant_province` FOREIGN KEY (`province_id`) REFERENCES `provinces` (`id`) ON DELETE CASCADE,
  CONSTRAINT `fk_permission_grant_set` FOREIGN KEY (`permission_set_id`) REFERENCES `permission_sets` (`id`) ON DELETE CASCADE,
  CONSTRAINT `fk_permission_grant_unit` FOREIGN KEY (`unit_id`) REFERENCES `units` (`id`) ON DELETE CASCADE,
  CONSTRAINT `fk_permission_grant_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE,
  CONSTRAINT `chk_permission_grant_scope` CHECK (`scope_type` = 'platform' and `province_id` is null and `unit_id` is null and `event_id` is null or `scope_type` = 'province' and `province_id` is not null and `unit_id` is null and `event_id` is null or `scope_type` = 'unit' and `province_id` is null and `unit_id` is not null and `event_id` is null or `scope_type` = 'event' and `province_id` is null and `unit_id` is null and `event_id` is not null)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `seat_assignments` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `dining_table_id` bigint(20) unsigned NOT NULL,
  `booking_id` bigint(20) unsigned NOT NULL,
  `guest_id` bigint(20) unsigned DEFAULT NULL,
  `seat_number` int(10) unsigned DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_booking_guest_seat` (`booking_id`,`guest_id`),
  KEY `fk_seat_table` (`dining_table_id`),
  KEY `fk_seat_guest` (`guest_id`),
  CONSTRAINT `fk_seat_booking` FOREIGN KEY (`booking_id`) REFERENCES `bookings` (`id`) ON DELETE CASCADE,
  CONSTRAINT `fk_seat_guest` FOREIGN KEY (`guest_id`) REFERENCES `guests` (`id`) ON DELETE CASCADE,
  CONSTRAINT `fk_seat_table` FOREIGN KEY (`dining_table_id`) REFERENCES `dining_tables` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `security_events` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `user_id` bigint(20) unsigned DEFAULT NULL,
  `actor_id` bigint(20) unsigned DEFAULT NULL,
  `event_key` varchar(60) NOT NULL,
  `occurred_at` datetime NOT NULL DEFAULT current_timestamp(),
  `notification_status` enum('none','queued','failed') NOT NULL DEFAULT 'none',
  PRIMARY KEY (`id`),
  KEY `idx_security_user` (`user_id`,`occurred_at`,`id`),
  KEY `actor_id` (`actor_id`),
  CONSTRAINT `security_events_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL,
  CONSTRAINT `security_events_ibfk_2` FOREIGN KEY (`actor_id`) REFERENCES `users` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `settings` (
  `unit_id` bigint(20) unsigned NOT NULL,
  `setting_key` varchar(100) NOT NULL,
  `setting_value` text DEFAULT NULL,
  `updated_by` bigint(20) unsigned DEFAULT NULL,
  `updated_at` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  PRIMARY KEY (`unit_id`,`setting_key`),
  KEY `fk_setting_user` (`updated_by`),
  CONSTRAINT `fk_setting_user` FOREIGN KEY (`updated_by`) REFERENCES `users` (`id`) ON DELETE SET NULL,
  CONSTRAINT `fk_settings_unit` FOREIGN KEY (`unit_id`) REFERENCES `units` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `source_installations` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `source_key` varchar(100) NOT NULL,
  `display_name` varchar(200) NOT NULL,
  `target_province_id` bigint(20) unsigned NOT NULL,
  `target_unit_id` bigint(20) unsigned NOT NULL,
  `status` enum('registered','importing','complete','failed') NOT NULL DEFAULT 'registered',
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  `updated_at` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_source_installation_key` (`source_key`),
  KEY `fk_source_province` (`target_province_id`),
  KEY `fk_source_unit` (`target_unit_id`),
  CONSTRAINT `fk_source_province` FOREIGN KEY (`target_province_id`) REFERENCES `provinces` (`id`),
  CONSTRAINT `fk_source_unit` FOREIGN KEY (`target_unit_id`) REFERENCES `units` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `system_jobs` (
  `unit_id` bigint(20) unsigned NOT NULL,
  `job_key` varchar(80) NOT NULL,
  `last_started_at` datetime DEFAULT NULL,
  `last_completed_at` datetime DEFAULT NULL,
  `last_status` enum('never','running','success','failed') NOT NULL DEFAULT 'never',
  `last_message` text DEFAULT NULL,
  PRIMARY KEY (`unit_id`,`job_key`),
  CONSTRAINT `fk_system_jobs_unit` FOREIGN KEY (`unit_id`) REFERENCES `units` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `tenant_document_categories` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `unit_id` bigint(20) unsigned NOT NULL,
  `label` varchar(100) NOT NULL,
  `name_key` varchar(100) GENERATED ALWAYS AS (lcase(trim(`label`))) STORED,
  `created_by` bigint(20) unsigned DEFAULT NULL,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_doc_category_unit_id` (`unit_id`,`id`),
  UNIQUE KEY `uq_doc_category_unit_name` (`unit_id`,`name_key`),
  KEY `created_by` (`created_by`),
  CONSTRAINT `tenant_document_categories_ibfk_1` FOREIGN KEY (`unit_id`) REFERENCES `units` (`id`) ON DELETE CASCADE,
  CONSTRAINT `tenant_document_categories_ibfk_2` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `tenant_document_versions` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `unit_id` bigint(20) unsigned NOT NULL,
  `document_id` bigint(20) unsigned NOT NULL,
  `version_number` int(10) unsigned NOT NULL,
  `storage_key` char(64) NOT NULL,
  `original_filename` varchar(200) NOT NULL,
  `mime_type` varchar(100) NOT NULL,
  `byte_size` int(10) unsigned NOT NULL,
  `sha256` char(64) NOT NULL,
  `uploaded_by` bigint(20) unsigned DEFAULT NULL,
  `uploaded_at` datetime NOT NULL DEFAULT current_timestamp(),
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_document_version` (`document_id`,`version_number`),
  UNIQUE KEY `uq_document_file` (`storage_key`),
  KEY `idx_document_versions` (`unit_id`,`document_id`),
  KEY `uploaded_by` (`uploaded_by`),
  CONSTRAINT `tenant_document_versions_ibfk_1` FOREIGN KEY (`unit_id`, `document_id`) REFERENCES `tenant_documents` (`unit_id`, `id`) ON DELETE CASCADE,
  CONSTRAINT `tenant_document_versions_ibfk_2` FOREIGN KEY (`uploaded_by`) REFERENCES `users` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `tenant_documents` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `unit_id` bigint(20) unsigned NOT NULL,
  `event_id` bigint(20) unsigned DEFAULT NULL,
  `category_key` varchar(32) NOT NULL,
  `custom_category_id` bigint(20) unsigned DEFAULT NULL,
  `title` varchar(200) NOT NULL,
  `description` varchar(500) DEFAULT NULL,
  `audience` enum('members','officers','administrators') NOT NULL DEFAULT 'members',
  `published_at` datetime DEFAULT NULL,
  `archived_at` datetime DEFAULT NULL,
  `deleted_at` datetime DEFAULT NULL,
  `created_by` bigint(20) unsigned DEFAULT NULL,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  `updated_at` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_tenant_documents_unit_id` (`unit_id`,`id`),
  KEY `idx_tenant_documents` (`unit_id`,`event_id`,`deleted_at`,`archived_at`),
  KEY `fk_tenant_document_event` (`event_id`),
  KEY `fk_tenant_document_custom` (`unit_id`,`custom_category_id`),
  KEY `created_by` (`created_by`),
  CONSTRAINT `fk_tenant_document_custom` FOREIGN KEY (`unit_id`, `custom_category_id`) REFERENCES `tenant_document_categories` (`unit_id`, `id`),
  CONSTRAINT `fk_tenant_document_event` FOREIGN KEY (`event_id`) REFERENCES `meetings` (`id`) ON DELETE SET NULL,
  CONSTRAINT `fk_tenant_document_unit` FOREIGN KEY (`unit_id`) REFERENCES `units` (`id`) ON DELETE CASCADE,
  CONSTRAINT `tenant_documents_ibfk_1` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `tenant_retention_queue` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `unit_id` bigint(20) unsigned NOT NULL,
  `schedule_id` bigint(20) unsigned NOT NULL,
  `data_class` varchar(40) NOT NULL,
  `source_id` bigint(20) unsigned NOT NULL,
  `due_at` datetime NOT NULL,
  `status` enum('pending','retain','approved','completed','skipped','failed') NOT NULL DEFAULT 'pending',
  `proposed_action` enum('review','anonymise','delete') NOT NULL,
  `decision_reason` varchar(500) DEFAULT NULL,
  `reviewed_by` bigint(20) unsigned DEFAULT NULL,
  `reviewed_at` datetime DEFAULT NULL,
  `completed_at` datetime DEFAULT NULL,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_retention_queue` (`schedule_id`,`data_class`,`source_id`),
  KEY `idx_retention_queue` (`unit_id`,`status`,`due_at`),
  KEY `fk_retention_queue_reviewer` (`reviewed_by`),
  CONSTRAINT `fk_retention_queue_reviewer` FOREIGN KEY (`reviewed_by`) REFERENCES `users` (`id`),
  CONSTRAINT `fk_retention_queue_schedule` FOREIGN KEY (`schedule_id`) REFERENCES `tenant_retention_schedules` (`id`),
  CONSTRAINT `fk_retention_queue_unit` FOREIGN KEY (`unit_id`) REFERENCES `units` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `tenant_retention_schedules` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `unit_id` bigint(20) unsigned NOT NULL,
  `data_class` enum('memberships','candidates','guest_directory','bookings','guests','messages','documents','welfare','finance') NOT NULL,
  `retention_months` smallint(5) unsigned NOT NULL,
  `due_action` enum('review','anonymise','delete') NOT NULL DEFAULT 'review',
  `obligation_type` enum('consent','operational','legal','accounting','safeguarding') NOT NULL DEFAULT 'operational',
  `legal_basis` varchar(255) DEFAULT NULL,
  `active` tinyint(1) NOT NULL DEFAULT 1,
  `updated_by` bigint(20) unsigned NOT NULL,
  `updated_at` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_retention_schedule` (`unit_id`,`data_class`),
  KEY `fk_retention_schedule_actor` (`updated_by`),
  CONSTRAINT `fk_retention_schedule_actor` FOREIGN KEY (`updated_by`) REFERENCES `users` (`id`),
  CONSTRAINT `fk_retention_schedule_unit` FOREIGN KEY (`unit_id`) REFERENCES `units` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `tenant_role_scopes` (
  `role_key` varchar(50) NOT NULL,
  `scope_type` enum('platform','province','unit') NOT NULL,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  PRIMARY KEY (`role_key`,`scope_type`),
  CONSTRAINT `fk_tenant_role_scope_role` FOREIGN KEY (`role_key`) REFERENCES `tenant_roles` (`role_key`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `tenant_roles` (
  `role_key` varchar(50) NOT NULL,
  `display_name` varchar(100) NOT NULL,
  `built_in` tinyint(1) NOT NULL DEFAULT 0,
  `active` tinyint(1) NOT NULL DEFAULT 1,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  `updated_at` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  PRIMARY KEY (`role_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `unit_candidates` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `unit_id` bigint(20) unsigned NOT NULL,
  `person_id` bigint(20) unsigned NOT NULL,
  `order_key` varchar(40) NOT NULL DEFAULT 'craft',
  `sponsor_person_id` bigint(20) unsigned DEFAULT NULL,
  `sponsor_name` varchar(200) DEFAULT NULL,
  `stage` enum('enquiry','meeting','application','ballot','approved','on_hold','closed','admitted') NOT NULL DEFAULT 'enquiry',
  `next_action` varchar(250) DEFAULT NULL,
  `agreed_contact_method` enum('email','phone','post','none') NOT NULL DEFAULT 'none',
  `review_date` date DEFAULT NULL,
  `closed_at` datetime DEFAULT NULL,
  `created_by` bigint(20) unsigned NOT NULL,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  `updated_at` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_unit_candidate` (`unit_id`,`person_id`,`order_key`),
  KEY `idx_candidate_stage` (`unit_id`,`stage`,`review_date`),
  KEY `fk_candidate_person` (`person_id`),
  KEY `fk_candidate_sponsor` (`sponsor_person_id`),
  KEY `fk_candidate_actor` (`created_by`),
  CONSTRAINT `fk_candidate_actor` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`),
  CONSTRAINT `fk_candidate_person` FOREIGN KEY (`person_id`) REFERENCES `people` (`id`),
  CONSTRAINT `fk_candidate_sponsor` FOREIGN KEY (`sponsor_person_id`) REFERENCES `people` (`id`) ON DELETE SET NULL,
  CONSTRAINT `fk_candidate_unit` FOREIGN KEY (`unit_id`) REFERENCES `units` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `unit_contacts` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `unit_id` bigint(20) unsigned NOT NULL,
  `person_id` bigint(20) unsigned NOT NULL,
  `source_guest_contact_id` bigint(20) unsigned DEFAULT NULL,
  `source_booking_id` bigint(20) unsigned DEFAULT NULL,
  `consent_text` text NOT NULL,
  `consent_version` varchar(40) NOT NULL,
  `consented_at` datetime NOT NULL,
  `permitted_unit_id` bigint(20) unsigned NOT NULL,
  `permitted_reuse` tinyint(1) NOT NULL DEFAULT 1,
  `permit_event_invitations` tinyint(1) NOT NULL DEFAULT 0,
  `permit_general_messages` tinyint(1) NOT NULL DEFAULT 0,
  `agreed_contact_method` enum('email','phone','post','none') NOT NULL DEFAULT 'none',
  `withdrawn_at` datetime DEFAULT NULL,
  `corrected_at` datetime DEFAULT NULL,
  `retention_expires_at` datetime NOT NULL,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  `updated_at` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_unit_contact` (`unit_id`,`person_id`),
  UNIQUE KEY `uq_contact_guest_source` (`source_guest_contact_id`),
  KEY `idx_unit_contact_consent` (`unit_id`,`withdrawn_at`,`retention_expires_at`),
  KEY `fk_unit_contact_scope` (`permitted_unit_id`),
  KEY `fk_unit_contact_person` (`person_id`),
  KEY `fk_unit_contact_booking` (`source_booking_id`),
  CONSTRAINT `fk_unit_contact_booking` FOREIGN KEY (`source_booking_id`) REFERENCES `bookings` (`id`) ON DELETE SET NULL,
  CONSTRAINT `fk_unit_contact_guest` FOREIGN KEY (`source_guest_contact_id`) REFERENCES `guest_contacts` (`id`) ON DELETE SET NULL,
  CONSTRAINT `fk_unit_contact_person` FOREIGN KEY (`person_id`) REFERENCES `people` (`id`),
  CONSTRAINT `fk_unit_contact_scope` FOREIGN KEY (`permitted_unit_id`) REFERENCES `units` (`id`) ON DELETE CASCADE,
  CONSTRAINT `fk_unit_contact_unit` FOREIGN KEY (`unit_id`) REFERENCES `units` (`id`) ON DELETE CASCADE,
  CONSTRAINT `chk_contact_unit_scope` CHECK (`unit_id` = `permitted_unit_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `unit_display_links` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `source_unit_id` bigint(20) unsigned NOT NULL,
  `target_unit_id` bigint(20) unsigned NOT NULL,
  `priority` int(10) unsigned NOT NULL DEFAULT 100,
  `active_from` date NOT NULL,
  `active_until` date DEFAULT NULL,
  `requested_by` bigint(20) unsigned DEFAULT NULL,
  `requested_at` datetime NOT NULL DEFAULT current_timestamp(),
  `accepted_by` bigint(20) unsigned DEFAULT NULL,
  `accepted_at` datetime DEFAULT NULL,
  `revoked_by` bigint(20) unsigned DEFAULT NULL,
  `revoked_at` datetime DEFAULT NULL,
  `updated_at` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_unit_display_direction` (`source_unit_id`,`target_unit_id`),
  KEY `idx_unit_display_source` (`source_unit_id`,`accepted_at`,`revoked_at`,`priority`),
  KEY `idx_unit_display_target` (`target_unit_id`,`accepted_at`,`revoked_at`),
  KEY `fk_unit_display_requester` (`requested_by`),
  KEY `fk_unit_display_accepter` (`accepted_by`),
  KEY `fk_unit_display_revoker` (`revoked_by`),
  CONSTRAINT `fk_unit_display_accepter` FOREIGN KEY (`accepted_by`) REFERENCES `users` (`id`) ON DELETE SET NULL,
  CONSTRAINT `fk_unit_display_requester` FOREIGN KEY (`requested_by`) REFERENCES `users` (`id`) ON DELETE SET NULL,
  CONSTRAINT `fk_unit_display_revoker` FOREIGN KEY (`revoked_by`) REFERENCES `users` (`id`) ON DELETE SET NULL,
  CONSTRAINT `fk_unit_display_source` FOREIGN KEY (`source_unit_id`) REFERENCES `units` (`id`) ON DELETE CASCADE,
  CONSTRAINT `fk_unit_display_target` FOREIGN KEY (`target_unit_id`) REFERENCES `units` (`id`) ON DELETE CASCADE,
  CONSTRAINT `chk_unit_display_distinct` CHECK (`source_unit_id` <> `target_unit_id`),
  CONSTRAINT `chk_unit_display_dates` CHECK (`active_until` is null or `active_until` >= `active_from`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `unit_domains_or_slugs` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `unit_id` bigint(20) unsigned NOT NULL,
  `identifier_type` enum('slug','domain') NOT NULL,
  `identifier_value` varchar(253) NOT NULL,
  `normalized_value` varchar(253) GENERATED ALWAYS AS (lcase(trim(`identifier_value`))) STORED,
  `is_primary` tinyint(1) NOT NULL DEFAULT 0,
  `redirect_to_primary` tinyint(1) NOT NULL DEFAULT 1,
  `verified_at` datetime DEFAULT NULL,
  `active` tinyint(1) NOT NULL DEFAULT 1,
  `primary_slot` varchar(80) GENERATED ALWAYS AS (case when `is_primary` = 1 and `active` = 1 then concat(`identifier_type`,':',`unit_id`) else NULL end) STORED,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  `updated_at` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_unit_identifier` (`identifier_type`,`normalized_value`),
  UNIQUE KEY `uq_unit_primary_identifier` (`primary_slot`),
  KEY `idx_unit_identifier_unit` (`unit_id`,`identifier_type`,`active`),
  CONSTRAINT `fk_unit_identifier_unit` FOREIGN KEY (`unit_id`) REFERENCES `units` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `unit_members` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `unit_id` bigint(20) unsigned NOT NULL,
  `person_id` bigint(20) unsigned NOT NULL,
  `order_key` varchar(40) NOT NULL DEFAULT 'craft',
  `membership_type` varchar(80) NOT NULL,
  `admission_route` enum('admitted','joining') NOT NULL,
  `state` enum('active','inactive','resigned','deceased') NOT NULL DEFAULT 'active',
  `start_date` date NOT NULL,
  `end_date` date DEFAULT NULL,
  `participation_status` varchar(80) DEFAULT NULL,
  `contact_email` tinyint(1) NOT NULL DEFAULT 0,
  `contact_post` tinyint(1) NOT NULL DEFAULT 0,
  `contact_phone` tinyint(1) NOT NULL DEFAULT 0,
  `created_by` bigint(20) unsigned NOT NULL,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  `updated_at` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_unit_member` (`unit_id`,`person_id`,`order_key`),
  KEY `idx_unit_member_state` (`unit_id`,`state`,`start_date`),
  KEY `fk_unit_member_person` (`person_id`),
  KEY `fk_unit_member_actor` (`created_by`),
  CONSTRAINT `fk_unit_member_actor` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`),
  CONSTRAINT `fk_unit_member_person` FOREIGN KEY (`person_id`) REFERENCES `people` (`id`),
  CONSTRAINT `fk_unit_member_unit` FOREIGN KEY (`unit_id`) REFERENCES `units` (`id`) ON DELETE CASCADE,
  CONSTRAINT `chk_member_dates` CHECK (`end_date` is null or `end_date` >= `start_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `unit_role_invitations` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `unit_id` bigint(20) unsigned NOT NULL,
  `email` varchar(254) NOT NULL,
  `role_key` varchar(50) NOT NULL,
  `token_hash` char(64) NOT NULL,
  `invited_by` bigint(20) unsigned DEFAULT NULL,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  `expires_at` datetime NOT NULL,
  `accepted_at` datetime DEFAULT NULL,
  `revoked_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_unit_role_token` (`token_hash`),
  KEY `idx_unit_role_pending` (`unit_id`,`email`,`expires_at`),
  KEY `invited_by` (`invited_by`),
  CONSTRAINT `unit_role_invitations_ibfk_1` FOREIGN KEY (`unit_id`) REFERENCES `units` (`id`) ON DELETE CASCADE,
  CONSTRAINT `unit_role_invitations_ibfk_2` FOREIGN KEY (`invited_by`) REFERENCES `users` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `unit_types` (
  `id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
  `type_key` varchar(40) NOT NULL,
  `display_name` varchar(100) NOT NULL,
  `body_noun` varchar(50) NOT NULL,
  `member_noun` varchar(50) NOT NULL,
  `built_in` tinyint(1) NOT NULL DEFAULT 0,
  `active` tinyint(1) NOT NULL DEFAULT 1,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  `updated_at` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_unit_type_key` (`type_key`),
  KEY `idx_unit_type_active` (`active`,`display_name`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `units` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `province_id` bigint(20) unsigned DEFAULT NULL,
  `unit_type_id` smallint(5) unsigned NOT NULL,
  `name` varchar(200) NOT NULL,
  `number` varchar(30) NOT NULL DEFAULT '',
  `slug` varchar(100) NOT NULL,
  `unit_type` varchar(50) NOT NULL DEFAULT 'lodge',
  `order_key` varchar(40) DEFAULT NULL,
  `active` tinyint(1) NOT NULL DEFAULT 1,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  `updated_at` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  `deleted_at` datetime DEFAULT NULL,
  `deleted_by` bigint(20) unsigned DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_unit_slug` (`slug`),
  UNIQUE KEY `uq_unit_province_name_number` (`province_id`,`name`,`number`),
  KEY `idx_unit_province_active` (`province_id`,`active`),
  KEY `idx_units_type_active` (`unit_type_id`,`active`),
  KEY `idx_units_province_visible` (`province_id`,`deleted_at`,`active`),
  KEY `fk_units_deleted_by` (`deleted_by`),
  CONSTRAINT `fk_unit_province` FOREIGN KEY (`province_id`) REFERENCES `provinces` (`id`),
  CONSTRAINT `fk_units_deleted_by` FOREIGN KEY (`deleted_by`) REFERENCES `users` (`id`) ON DELETE SET NULL,
  CONSTRAINT `fk_units_unit_type` FOREIGN KEY (`unit_type_id`) REFERENCES `unit_types` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `user_memberships` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `user_id` bigint(20) unsigned NOT NULL,
  `scope_type` enum('platform','province','unit') NOT NULL,
  `province_id` bigint(20) unsigned DEFAULT NULL,
  `unit_id` bigint(20) unsigned DEFAULT NULL,
  `role` varchar(50) NOT NULL,
  `active` tinyint(1) NOT NULL DEFAULT 1,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  `updated_at` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  `scope_key` varchar(80) GENERATED ALWAYS AS (case `scope_type` when 'platform' then 'platform' when 'province' then concat('province:',`province_id`) else concat('unit:',`unit_id`) end) STORED,
  `expires_at` datetime DEFAULT NULL,
  `revoked_at` datetime DEFAULT NULL,
  `last_used_at` datetime DEFAULT NULL,
  `reviewed_at` datetime DEFAULT NULL,
  `review_due_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_user_membership` (`user_id`,`scope_type`,`scope_key`),
  KEY `idx_membership_province` (`province_id`,`user_id`,`active`),
  KEY `idx_membership_unit` (`unit_id`,`user_id`,`active`),
  KEY `idx_membership_role_scope` (`role`,`scope_type`),
  KEY `idx_membership_expiry` (`user_id`,`active`,`expires_at`),
  KEY `idx_membership_review` (`scope_type`,`province_id`,`unit_id`,`review_due_at`),
  CONSTRAINT `fk_membership_province` FOREIGN KEY (`province_id`) REFERENCES `provinces` (`id`) ON DELETE CASCADE,
  CONSTRAINT `fk_membership_role_scope` FOREIGN KEY (`role`, `scope_type`) REFERENCES `tenant_role_scopes` (`role_key`, `scope_type`),
  CONSTRAINT `fk_membership_unit` FOREIGN KEY (`unit_id`) REFERENCES `units` (`id`) ON DELETE CASCADE,
  CONSTRAINT `fk_membership_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE,
  CONSTRAINT `chk_membership_scope` CHECK (`scope_type` = 'platform' and `province_id` is null and `unit_id` is null or `scope_type` = 'province' and `province_id` is not null and `unit_id` is null or `scope_type` = 'unit' and `province_id` is null and `unit_id` is not null)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `users` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `email` varchar(254) NOT NULL,
  `display_name` varchar(150) NOT NULL,
  `password_hash` varchar(255) NOT NULL,
  `role` enum('admin','secretary','dining','treasurer','worshipful_master','readonly','member') NOT NULL DEFAULT 'member',
  `active` tinyint(1) NOT NULL DEFAULT 1,
  `two_factor_enabled` tinyint(1) NOT NULL DEFAULT 0,
  `failed_logins` int(10) unsigned NOT NULL DEFAULT 0,
  `locked_until` datetime DEFAULT NULL,
  `last_login_at` datetime DEFAULT NULL,
  `password_changed_at` datetime DEFAULT NULL,
  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  `updated_at` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_user_email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!50001 DROP VIEW IF EXISTS `active_user_memberships`*/;
/*!50001 SET @saved_cs_client          = @@character_set_client */;
/*!50001 SET @saved_cs_results         = @@character_set_results */;
/*!50001 SET @saved_col_connection     = @@collation_connection */;
/*!50001 SET character_set_client      = utf8mb4 */;
/*!50001 SET character_set_results     = utf8mb4 */;
/*!50001 SET collation_connection      = utf8mb4_general_ci */;
/*!50001 CREATE ALGORITHM=UNDEFINED */
/*!50013 SQL SECURITY INVOKER */
/*!50001 VIEW `active_user_memberships` AS select `m`.`id` AS `id`,`m`.`user_id` AS `user_id`,`m`.`scope_type` AS `scope_type`,`m`.`province_id` AS `province_id`,`m`.`unit_id` AS `unit_id`,`m`.`role` AS `role`,`m`.`active` AS `active`,`m`.`created_at` AS `created_at`,`m`.`updated_at` AS `updated_at`,`m`.`scope_key` AS `scope_key`,`m`.`expires_at` AS `expires_at`,`m`.`revoked_at` AS `revoked_at`,`m`.`last_used_at` AS `last_used_at`,`m`.`reviewed_at` AS `reviewed_at`,`m`.`review_due_at` AS `review_due_at` from `user_memberships` `m` where `m`.`active` = 1 and `m`.`revoked_at` is null and (`m`.`expires_at` is null or `m`.`expires_at` > current_timestamp()) and exists(select 1 from `users` `u` where `u`.`id` = `m`.`user_id` and `u`.`active` = 1 limit 1) */;
/*!50001 SET character_set_client      = @saved_cs_client */;
/*!50001 SET character_set_results     = @saved_cs_results */;
/*!50001 SET collation_connection      = @saved_col_connection */;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;

/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;



/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;

LOCK TABLES `platform_order_rank_settings` WRITE;
/*!40000 ALTER TABLE `platform_order_rank_settings` DISABLE KEYS */;
INSERT INTO `platform_order_rank_settings` (`order_key`, `salutations`, `salutation_rank_links`, `masonic_ranks`, `active_provincial_ranks`, `past_provincial_ranks`, `active_grand_ranks`, `past_grand_ranks`, `updated_at`) VALUES ('craft','Bro\nWBro\nVWBro\nRWBro\nMWBro','{\"Bro\":[{\"scope\":\"masonic\",\"rank\":\"Entered Apprentice\"},{\"scope\":\"masonic\",\"rank\":\"Fellow Craft\"},{\"scope\":\"masonic\",\"rank\":\"Master Mason\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Steward\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Tyler\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Pursuivant\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Pursuivant\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Tyler\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Steward\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Deputy Grand Organist\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Assistant Grand Standard Bearer\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Standard Bearer\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Standard Bearer\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Assistant Grand Standard Bearer\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Deputy Grand Organist\"}],\"WBro\":[{\"scope\":\"provincial\",\"rank\":\"Deputy Provincial Grand Master\"},{\"scope\":\"provincial\",\"rank\":\"Assistant Provincial Grand Master\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Senior Grand Warden\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Junior Grand Warden\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Chaplain\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Treasurer\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Registrar\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Secretary\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Director of Ceremonies\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Sword Bearer\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Superintendent of Works\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Deputy Grand Chaplain\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Deputy Grand Registrar\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Deputy Grand Secretary\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Deputy Grand Director of Ceremonies\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Deputy Grand Sword Bearer\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Deputy Grand Superintendent of Works\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Assistant Grand Inspector\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Almoner\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Charity Steward\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Membership Officer\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Communications Officer\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Learning and Development Officer\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Senior Grand Deacon\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Junior Grand Deacon\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Assistant Grand Chaplain\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Assistant Grand Registrar\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Assistant Grand Secretary\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Assistant Grand Director of Ceremonies\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Assistant Grand Sword Bearer\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Assistant Grand Superintendent of Works\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Organise\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Standard Bearer\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Assistant Grand Standard Bearer\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Deputy Grand Organist\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Pursuivant\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Tyler\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Steward\"},{\"scope\":\"grand\",\"rank\":\"Grand Treasurer\"},{\"scope\":\"grand\",\"rank\":\"Deputy Grand Chaplain\"},{\"scope\":\"grand\",\"rank\":\"Deputy Grand Registrar\"},{\"scope\":\"grand\",\"rank\":\"Deputy Grand Director of Ceremonies\"},{\"scope\":\"grand\",\"rank\":\"Deputy Grand Sword Bearer\"},{\"scope\":\"grand\",\"rank\":\"Deputy Grand Superintendent of Works\"},{\"scope\":\"grand\",\"rank\":\"Senior Grand Deacon\"},{\"scope\":\"grand\",\"rank\":\"Junior Grand Deacon\"},{\"scope\":\"grand\",\"rank\":\"Assistant Grand Chaplain\"},{\"scope\":\"grand\",\"rank\":\"Assistant Grand Registrar\"},{\"scope\":\"grand\",\"rank\":\"Assistant Grand Directors of Ceremonies\"},{\"scope\":\"grand\",\"rank\":\"Assistant Grand Sword Bearers\"},{\"scope\":\"grand\",\"rank\":\"Assistant Grand Superintendents of Works\"},{\"scope\":\"grand\",\"rank\":\"Grand Organist\"},{\"scope\":\"grand\",\"rank\":\"Grand Standard Bearer\"},{\"scope\":\"grand\",\"rank\":\"Deputy Grand Organist\"},{\"scope\":\"grand\",\"rank\":\"Grand Pursuivant\"},{\"scope\":\"grand\",\"rank\":\"Assistant Grand Pursiuvant\"},{\"scope\":\"grand\",\"rank\":\"Grand Steward\"},{\"scope\":\"provincial\",\"rank\":\"Past Deputy Provincial Grand Master\"},{\"scope\":\"provincial\",\"rank\":\"Past Assistant Provincial Grand Master\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Senior Grand Warden\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Junior Grand Warden\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Chaplain\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Treasurer\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Registrar\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Secretary\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Director of Ceremonies\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Sword Bearer\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Superintendent of Works\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Deputy Grand Chaplain\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Deputy Grand Registrar\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Deputy Grand Secretary\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Deputy Grand Director of Ceremonies\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Deputy Grand Sword Bearer\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Deputy Grand Superintendent of Works\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Assistant Grand Inspector\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Almoner\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Charity Steward\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Treasurer\"},{\"scope\":\"grand\",\"rank\":\"Past Deputy Grand Chaplain\"},{\"scope\":\"grand\",\"rank\":\"Past Deputy Grand Registrar\"},{\"scope\":\"grand\",\"rank\":\"Past Deputy Grand Director of Ceremonies\"},{\"scope\":\"grand\",\"rank\":\"Past Deputy Grand Sword Bearer\"},{\"scope\":\"grand\",\"rank\":\"Past Deputy Grand Superintendent of Works\"},{\"scope\":\"grand\",\"rank\":\"Past Senior Grand Deacon\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Membership Officer\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Communications Officer\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Learning and Development Officer\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Senior Grand Deacon\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Junior Grand Deacon\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Assistant Grand Chaplain\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Assistant Grand Registrar\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Assistant Grand Secretary\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Assistant Grand Director of Ceremonies\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Assistant Grand Sword Bearer\"},{\"scope\":\"grand\",\"rank\":\"Past Junior Grand Deacon\"},{\"scope\":\"grand\",\"rank\":\"Past Assistant Grand Chaplain\"},{\"scope\":\"grand\",\"rank\":\"Past Assistant Grand Registrar\"},{\"scope\":\"grand\",\"rank\":\"Past Assistant Grand Directors of Ceremonies\"},{\"scope\":\"grand\",\"rank\":\"Past Assistant Grand Sword Bearers\"},{\"scope\":\"grand\",\"rank\":\"Past Assistant Grand Superintendents of Works\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Organist\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Standard Bearer\"},{\"scope\":\"grand\",\"rank\":\"Past Deputy Grand Organist\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Pursuivant\"},{\"scope\":\"grand\",\"rank\":\"Past Assistant Grand Pursiuvant\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Steward\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Assistant Grand Superintendent of Works\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Organise\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Standard Bearer\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Assistant Grand Standard Bearer\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Deputy Grand Organist\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Pursuivant\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Tyler\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Steward\"},{\"scope\":\"masonic\",\"rank\":\"Worshipful Master\"},{\"scope\":\"masonic\",\"rank\":\"Immediate Past Master\"},{\"scope\":\"masonic\",\"rank\":\"Past Master\"}],\"VWBro\":[{\"scope\":\"provincial\",\"rank\":\"Deputy Provincial Grand Master\"},{\"scope\":\"provincial\",\"rank\":\"Past Deputy Provincial Grand Master\"},{\"scope\":\"grand\",\"rank\":\"Grand Chaplain\"},{\"scope\":\"grand\",\"rank\":\"Grand Registrar\"},{\"scope\":\"grand\",\"rank\":\"Grand Secretary\"},{\"scope\":\"grand\",\"rank\":\"Grand Chancellor\"},{\"scope\":\"grand\",\"rank\":\"President of the Masonic Charitable Foundation\"},{\"scope\":\"grand\",\"rank\":\"Deputy President of the Board of General Purposes\"},{\"scope\":\"grand\",\"rank\":\"Deputy President of the Masonic Charitable Foundation\"},{\"scope\":\"grand\",\"rank\":\"Grand Director of Ceremonies\"},{\"scope\":\"grand\",\"rank\":\"Grand Sword Bearer\"},{\"scope\":\"grand\",\"rank\":\"Grand Superintendent of Works\"},{\"scope\":\"grand\",\"rank\":\"Grand Inspector\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Chaplain\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Registrar\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Secretary\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Chancellor\"},{\"scope\":\"grand\",\"rank\":\"Past President of the Masonic Charitable Foundation\"},{\"scope\":\"grand\",\"rank\":\"Past Deputy President of the Board of General Purposes\"},{\"scope\":\"grand\",\"rank\":\"Past Deputy President of the Masonic Charitable Foundation\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Director of Ceremonies\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Sword Bearer\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Superintendent of Works\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Inspector\"}],\"RWBro\":[{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Master\"},{\"scope\":\"grand\",\"rank\":\"Deputy Grand Master\"},{\"scope\":\"grand\",\"rank\":\"Assistant Grand Master\"},{\"scope\":\"grand\",\"rank\":\"Senior Grand Warden\"},{\"scope\":\"grand\",\"rank\":\"Junior Grand Warden\"},{\"scope\":\"grand\",\"rank\":\"President of the Board of General Purposes\"},{\"scope\":\"grand\",\"rank\":\"Past Deputy Grand Master\"},{\"scope\":\"grand\",\"rank\":\"Past Assistant Grand Master\"},{\"scope\":\"grand\",\"rank\":\"Past Senior Grand Warden\"},{\"scope\":\"grand\",\"rank\":\"Past Junior Grand Warden\"},{\"scope\":\"grand\",\"rank\":\"Past President of the Board of General Purposes\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Master\"}],\"MWBro\":[{\"scope\":\"grand\",\"rank\":\"Grand Master\"},{\"scope\":\"grand\",\"rank\":\"Pro Grand Master\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Master\"},{\"scope\":\"grand\",\"rank\":\"Past Pro Grand Master\"}]}','Entered Apprentice\nFellow Craft\nMaster Mason\nWorshipful Master\nImmediate Past Master\nPast Master','Provincial Grand Master\nDeputy Provincial Grand Master\nAssistant Provincial Grand Master\nProvincial Senior Grand Warden\nProvincial Junior Grand Warden\nProvincial Grand Chaplain\nProvincial Grand Treasurer\nProvincial Grand Registrar\nProvincial Grand Secretary\nProvincial Grand Director of Ceremonies\nProvincial Grand Sword Bearer\nProvincial Grand Superintendent of Works\nProvincial Deputy Grand Chaplain\nProvincial Deputy Grand Registrar\nProvincial Deputy Grand Secretary\nProvincial Deputy Grand Director of Ceremonies\nProvincial Deputy Grand Sword Bearer\nProvincial Deputy Grand Superintendent of Works\nProvincial Assistant Grand Inspector\nProvincial Grand Almoner\nProvincial Grand Charity Steward\nProvincial Grand Membership Officer\nProvincial Grand Communications Officer\nProvincial Grand Learning and Development Officer\nProvincial Senior Grand Deacon\nProvincial Junior Grand Deacon\nProvincial Assistant Grand Chaplain\nProvincial Assistant Grand Registrar\nProvincial Assistant Grand Secretary\nProvincial Assistant Grand Director of Ceremonies\nProvincial Assistant Grand Sword Bearer\nProvincial Assistant Grand Superintendent of Works\nProvincial Grand Organise\nProvincial Standard Bearer\nProvincial Assistant Grand Standard Bearer\nProvincial Deputy Grand Organist\nProvincial Grand Pursuivant\nProvincial Grand Tyler\nProvincial Grand Steward','Past Provincial Grand Master\nPast Deputy Provincial Grand Master\nPast Assistant Provincial Grand Master\nPast Provincial Senior Grand Warden\nPast Provincial Junior Grand Warden\nPast Provincial Grand Chaplain\nPast Provincial Grand Treasurer\nPast Provincial Grand Registrar\nPast Provincial Grand Secretary\nPast Provincial Grand Director of Ceremonies\nPast Provincial Grand Sword Bearer\nPast Provincial Grand Superintendent of Works\nPast Provincial Deputy Grand Chaplain\nPast Provincial Deputy Grand Registrar\nPast Provincial Deputy Grand Secretary\nPast Provincial Deputy Grand Director of Ceremonies\nPast Provincial Deputy Grand Sword Bearer\nPast Provincial Deputy Grand Superintendent of Works\nPast Provincial Assistant Grand Inspector\nPast Provincial Grand Almoner\nPast Provincial Grand Charity Steward\nPast Provincial Grand Membership Officer\nPast Provincial Grand Communications Officer\nPast Provincial Grand Learning and Development Officer\nPast Provincial Senior Grand Deacon\nPast Provincial Junior Grand Deacon\nPast Provincial Assistant Grand Chaplain\nPast Provincial Assistant Grand Registrar\nPast Provincial Assistant Grand Secretary\nPast Provincial Assistant Grand Director of Ceremonies\nPast Provincial Assistant Grand Sword Bearer\nPast Provincial Assistant Grand Superintendent of Works\nPast Provincial Grand Organise\nPast Provincial Standard Bearer\nPast Provincial Assistant Grand Standard Bearer\nPast Provincial Deputy Grand Organist\nPast Provincial Grand Pursuivant\nPast Provincial Grand Tyler\nPast Provincial Grand Steward','Grand Master\nPro Grand Master\nDeputy Grand Master\nAssistant Grand Master\nSenior Grand Warden\nJunior Grand Warden\nPresident of the Board of General Purposes\nGrand Chaplain\nGrand Registrar\nGrand Secretary\nGrand Chancellor\nPresident of the Masonic Charitable Foundation\nDeputy President of the Board of General Purposes\nDeputy President of the Masonic Charitable Foundation\nGrand Director of Ceremonies\nGrand Sword Bearer\nGrand Superintendent of Works\nGrand Inspector\nGrand Treasurer\nDeputy Grand Chaplain\nDeputy Grand Registrar\nDeputy Grand Director of Ceremonies\nDeputy Grand Sword Bearer\nDeputy Grand Superintendent of Works\nSenior Grand Deacon\nJunior Grand Deacon\nAssistant Grand Chaplain\nAssistant Grand Registrar\nAssistant Grand Directors of Ceremonies\nAssistant Grand Sword Bearers\nAssistant Grand Superintendents of Works\nGrand Organist\nGrand Standard Bearer\nDeputy Grand Organist\nGrand Pursuivant\nAssistant Grand Pursiuvant\nGrand Steward','Past Grand Master\nPast Pro Grand Master\nPast Deputy Grand Master\nPast Assistant Grand Master\nPast Senior Grand Warden\nPast Junior Grand Warden\nPast President of the Board of General Purposes\nPast Grand Chaplain\nPast Grand Registrar\nPast Grand Secretary\nPast Grand Chancellor\nPast President of the Masonic Charitable Foundation\nPast Deputy President of the Board of General Purposes\nPast Deputy President of the Masonic Charitable Foundation\nPast Grand Director of Ceremonies\nPast Grand Sword Bearer\nPast Grand Superintendent of Works\nPast Grand Inspector\nPast Grand Treasurer\nPast Deputy Grand Chaplain\nPast Deputy Grand Registrar\nPast Deputy Grand Director of Ceremonies\nPast Deputy Grand Sword Bearer\nPast Deputy Grand Superintendent of Works\nPast Senior Grand Deacon\nPast Junior Grand Deacon\nPast Assistant Grand Chaplain\nPast Assistant Grand Registrar\nPast Assistant Grand Directors of Ceremonies\nPast Assistant Grand Sword Bearers\nPast Assistant Grand Superintendents of Works\nPast Grand Organist\nPast Grand Standard Bearer\nPast Deputy Grand Organist\nPast Grand Pursuivant\nPast Assistant Grand Pursiuvant\nPast Grand Steward','2026-09-27 21:01:01'),('mark','Bro\nWBro\nVWBro\nRWBro\nMWBro','{\"Bro\":[{\"scope\":\"masonic\",\"rank\":\"Mark Master Mason\"},{\"scope\":\"masonic\",\"rank\":\"Worshipful Master\"},{\"scope\":\"masonic\",\"rank\":\"Immediate Past Master\"},{\"scope\":\"masonic\",\"rank\":\"Past Master\"},{\"scope\":\"grand\",\"rank\":\"Deputy Grand Tyler\"},{\"scope\":\"grand\",\"rank\":\"Grand Tyler\"},{\"scope\":\"grand\",\"rank\":\"Grand Steward\"},{\"scope\":\"grand\",\"rank\":\"Grand Inner Guard\"},{\"scope\":\"grand\",\"rank\":\"Assistant Grand Inner Guard\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Inner Guard\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Steward\"},{\"scope\":\"grand\",\"rank\":\"Past Assistant Grand Inner Guard\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Tyler\"},{\"scope\":\"grand\",\"rank\":\"Past Deputy Grand Tyler\"}],\"WBro\":[{\"scope\":\"grand\",\"rank\":\"Grand Inspector of Works\"},{\"scope\":\"grand\",\"rank\":\"Grand Sword Bearer\"},{\"scope\":\"grand\",\"rank\":\"Deputy Grand Chaplain\"},{\"scope\":\"grand\",\"rank\":\"Deputy President of the General Board\"},{\"scope\":\"grand\",\"rank\":\"Deputy Grand Registrar\"},{\"scope\":\"grand\",\"rank\":\"Deputy President of the Mark Benevolent Fund\"},{\"scope\":\"grand\",\"rank\":\"Deputy Grand Secretary\"},{\"scope\":\"grand\",\"rank\":\"Deputy Grand Director of Ceremonies\"},{\"scope\":\"grand\",\"rank\":\"Deputy Grand Inspectors of Works\"},{\"scope\":\"grand\",\"rank\":\"Deputy Grand Sword Bearer\"},{\"scope\":\"grand\",\"rank\":\"Grand Senior Deacon\"},{\"scope\":\"grand\",\"rank\":\"Grand Junior Deacon\"},{\"scope\":\"grand\",\"rank\":\"Assistant Grand Chaplain\"},{\"scope\":\"grand\",\"rank\":\"Assistant Grand Registrar\"},{\"scope\":\"grand\",\"rank\":\"Assistant Grand Secretary\"},{\"scope\":\"grand\",\"rank\":\"Assistant Grand Directors of Ceremonies\"},{\"scope\":\"grand\",\"rank\":\"Assistant Grand Inspector of Works\"},{\"scope\":\"grand\",\"rank\":\"Assistant Grand Sword Bearers\"},{\"scope\":\"grand\",\"rank\":\"Grand Librarian\"},{\"scope\":\"grand\",\"rank\":\"Grand Organist\"},{\"scope\":\"grand\",\"rank\":\"Assistant Grand Standard Bearer\"},{\"scope\":\"grand\",\"rank\":\"Grand Standard Bearer\"},{\"scope\":\"grand\",\"rank\":\"Deputy Grand Organist\"},{\"scope\":\"grand\",\"rank\":\"Grand Inner Guard\"},{\"scope\":\"grand\",\"rank\":\"Assistant Grand Inner Guard\"},{\"scope\":\"grand\",\"rank\":\"Grand Steward\"},{\"scope\":\"grand\",\"rank\":\"Deputy Grand Tyler\"},{\"scope\":\"grand\",\"rank\":\"Grand Tyler\"},{\"scope\":\"grand\",\"rank\":\"Past Deputy Grand Tyler\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Tyler\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Steward\"},{\"scope\":\"grand\",\"rank\":\"Past Assistant Grand Inner Guard\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Inner Guard\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Inspector of Works\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Sword Bearer\"},{\"scope\":\"grand\",\"rank\":\"Past Deputy Grand Chaplain\"},{\"scope\":\"grand\",\"rank\":\"Past Deputy President of the General Board\"},{\"scope\":\"grand\",\"rank\":\"Past Deputy Grand Registrar\"},{\"scope\":\"grand\",\"rank\":\"Past Deputy President of the Mark Benevolent Fund\"},{\"scope\":\"grand\",\"rank\":\"Past Deputy Grand Secretary\"},{\"scope\":\"grand\",\"rank\":\"Past Deputy Grand Director of Ceremonies\"},{\"scope\":\"grand\",\"rank\":\"Past Deputy Grand Inspectors of Works\"},{\"scope\":\"grand\",\"rank\":\"Past Deputy Grand Sword Bearer\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Senior Deacon\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Junior Deacon\"},{\"scope\":\"grand\",\"rank\":\"Past Assistant Grand Chaplain\"},{\"scope\":\"grand\",\"rank\":\"Past Assistant Grand Registrar\"},{\"scope\":\"grand\",\"rank\":\"Past Assistant Grand Secretary\"},{\"scope\":\"grand\",\"rank\":\"Past Assistant Grand Directors of Ceremonies\"},{\"scope\":\"grand\",\"rank\":\"Past Assistant Grand Inspector of Works\"},{\"scope\":\"grand\",\"rank\":\"Past Assistant Grand Sword Bearers\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Librarian\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Organist\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Standard Bearer\"},{\"scope\":\"grand\",\"rank\":\"Past Assistant Grand Standard Bearer\"},{\"scope\":\"grand\",\"rank\":\"Past Deputy Grand Organist\"},{\"scope\":\"provincial\",\"rank\":\"Assistant Provincial Grand Master\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Senior Warden\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Junior Warden\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Master Overseer\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Senior Overseer\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Chaplain\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Junior Overseer\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Treasurer\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Registrar\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Secretary\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Director of Ceremonies\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Senior Deacon\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Junior Deacon\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Assistant Grand Chaplain\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Assistant Grand Charity Steward\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Assistant Grand Almoner\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Organist\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Standard Bearer\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Assistant Grand Standard Bearer\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Inner Guard\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Steward\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Tyler\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Tyler\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Steward\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Inner Guard\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Assistant Grand Standard Bearer\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Standard Bearer\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Organist\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Assistant Grand Almoner\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Assistant Grand Charity Steward\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Assistant Grand Chaplain\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Junior Deacon\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Senior Deacon\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Director of Ceremonies\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Secretary\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Registrar\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Treasurer\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Chaplain\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Junior Overseer\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Senior Overseer\"},{\"scope\":\"provincial\",\"rank\":\"Past Assistant Provincial Grand Master\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Senior Warden\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Junior Warden\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Master Overseer\"},{\"scope\":\"masonic\",\"rank\":\"Worshipful Master\"},{\"scope\":\"masonic\",\"rank\":\"Immediate Past Master\"},{\"scope\":\"masonic\",\"rank\":\"Past Master\"}],\"VWBro\":[{\"scope\":\"grand\",\"rank\":\"Grand Inspector\"},{\"scope\":\"grand\",\"rank\":\"Grand Director of Ceremonies\"},{\"scope\":\"grand\",\"rank\":\"Grand Secretary\"},{\"scope\":\"grand\",\"rank\":\"President of the Mark Benevolent Fund\"},{\"scope\":\"grand\",\"rank\":\"Grand Registrar\"},{\"scope\":\"grand\",\"rank\":\"Grand Treasurer\"},{\"scope\":\"grand\",\"rank\":\"President of the Board of General Purposes\"},{\"scope\":\"grand\",\"rank\":\"Grand Chaplain\"},{\"scope\":\"grand\",\"rank\":\"Grand Junior Overseer\"},{\"scope\":\"grand\",\"rank\":\"Grand Senior Overseer\"},{\"scope\":\"grand\",\"rank\":\"Grand Master Overseer\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Inspector\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Master Overseer\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Senior Overseer\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Junior Overseer\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Chaplain\"},{\"scope\":\"grand\",\"rank\":\"Past President of the Board of General Purposes\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Treasurer\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Registrar\"},{\"scope\":\"grand\",\"rank\":\"Past President of the Mark Benevolent Fund\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Secretary\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Director of Ceremonies\"},{\"scope\":\"provincial\",\"rank\":\"Deputy Provincial Grand Master\"},{\"scope\":\"provincial\",\"rank\":\"Past Deputy Provincial Grand Master\"}],\"RWBro\":[{\"scope\":\"grand\",\"rank\":\"Deputy Grand Master\"},{\"scope\":\"grand\",\"rank\":\"Assistant Grand Master\"},{\"scope\":\"grand\",\"rank\":\"Senior Grand Warden\"},{\"scope\":\"grand\",\"rank\":\"Junior Grand Warden\"},{\"scope\":\"grand\",\"rank\":\"Past Deputy Grand Master\"},{\"scope\":\"grand\",\"rank\":\"Past Assistant Grand Master\"},{\"scope\":\"grand\",\"rank\":\"Past Senior Grand Warden\"},{\"scope\":\"grand\",\"rank\":\"Past Junior Grand Warden\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Master\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Master\"}],\"MWBro\":[{\"scope\":\"grand\",\"rank\":\"Grand Master\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Master\"},{\"scope\":\"grand\",\"rank\":\"Pro Grand Master\"}]}','Mark Master Mason\nWorshipful Master\nImmediate Past Master\nPast Master','Provincial Grand Master\nDeputy Provincial Grand Master\nAssistant Provincial Grand Master\nProvincial Grand Senior Warden\nProvincial Grand Junior Warden\nProvincial Grand Master Overseer\nProvincial Grand Senior Overseer\nProvincial Grand Junior Overseer\nProvincial Grand Chaplain\nProvincial Grand Treasurer\nProvincial Grand Registrar\nProvincial Grand Secretary\nProvincial Grand Director of Ceremonies\nProvincial Grand Senior Deacon\nProvincial Grand Junior Deacon\nProvincial Assistant Grand Chaplain\nProvincial Assistant Grand Charity Steward\nProvincial Assistant Grand Almoner\nProvincial Grand Organist\nProvincial Grand Standard Bearer\nProvincial Assistant Grand Standard Bearer\nProvincial Grand Inner Guard\nProvincial Grand Steward\nProvincial Grand Tyler','Past Provincial Grand Master\nPast Deputy Provincial Grand Master\nPast Assistant Provincial Grand Master\nPast Provincial Grand Senior Warden\nPast Provincial Grand Junior Warden\nPast Provincial Grand Master Overseer\nPast Provincial Grand Senior Overseer\nPast Provincial Grand Junior Overseer\nPast Provincial Grand Chaplain\nPast Provincial Grand Treasurer\nPast Provincial Grand Registrar\nPast Provincial Grand Secretary\nPast Provincial Grand Director of Ceremonies\nPast Provincial Grand Senior Deacon\nPast Provincial Grand Junior Deacon\nPast Provincial Assistant Grand Chaplain\nPast Provincial Assistant Grand Charity Steward\nPast Provincial Assistant Grand Almoner\nPast Provincial Grand Organist\nPast Provincial Grand Standard Bearer\nPast Provincial Assistant Grand Standard Bearer\nPast Provincial Grand Inner Guard\nPast Provincial Grand Steward\nPast Provincial Grand Tyler','Grand Master\nPro Grand Master\nDeputy Grand Master\nAssistant Grand Master\nSenior Grand Warden\nJunior Grand Warden\nGrand Inspector\nGrand Master Overseer\nGrand Senior Overseer\nGrand Junior Overseer\nGrand Chaplain\nPresident of the Board of General Purposes\nGrand Treasurer\nGrand Registrar\nPresident of the Mark Benevolent Fund\nGrand Secretary\nGrand Director of Ceremonies\nGrand Inspector of Works\nGrand Sword Bearer\nDeputy Grand Chaplain\nDeputy President of the General Board\nDeputy Grand Registrar\nDeputy President of the Mark Benevolent Fund\nDeputy Grand Secretary\nDeputy Grand Director of Ceremonies\nDeputy Grand Inspectors of Works\nDeputy Grand Sword Bearer\nGrand Senior Deacon\nGrand Junior Deacon\nAssistant Grand Chaplain\nAssistant Grand Registrar\nAssistant Grand Secretary\nAssistant Grand Directors of Ceremonies\nAssistant Grand Inspector of Works\nAssistant Grand Sword Bearers\nGrand Librarian\nGrand Organist\nGrand Standard Bearer\nAssistant Grand Standard Bearer\nDeputy Grand Organist\nGrand Inner Guard\nAssistant Grand Inner Guard\nGrand Steward\nGrand Tyler\nDeputy Grand Tyler','Past Grand Master\nPro Grand Master\nPast Deputy Grand Master\nPast Assistant Grand Master\nPast Senior Grand Warden\nPast Junior Grand Warden\nPast Grand Inspector\nPast Grand Master Overseer\nPast Grand Senior Overseer\nPast Grand Junior Overseer\nPast Grand Chaplain\nPast President of the Board of General Purposes\nPast Grand Treasurer\nPast Grand Registrar\nPast President of the Mark Benevolent Fund\nPast Grand Secretary\nPast Grand Director of Ceremonies\nPast Grand Inspector of Works\nPast Grand Sword Bearer\nPast Deputy Grand Chaplain\nPast Deputy President of the General Board\nPast Deputy Grand Registrar\nPast Deputy President of the Mark Benevolent Fund\nPast Deputy Grand Secretary\nPast Deputy Grand Director of Ceremonies\nPast Deputy Grand Inspectors of Works\nPast Deputy Grand Sword Bearer\nPast Grand Senior Deacon\nPast Grand Junior Deacon\nPast Assistant Grand Chaplain\nPast Assistant Grand Registrar\nPast Assistant Grand Secretary\nPast Assistant Grand Directors of Ceremonies\nPast Assistant Grand Inspector of Works\nPast Assistant Grand Sword Bearers\nPast Grand Librarian\nPast Grand Organist\nPast Grand Standard Bearer\nPast Assistant Grand Standard Bearer\nPast Deputy Grand Organist\nPast Grand Inner Guard\nPast Assistant Grand Inner Guard\nPast Grand Steward\nPast Grand Tyler\nPast Deputy Grand Tyler','2026-09-27 21:01:01'),('royal_arch','Comp\nEComp\nMEComp','{\"Comp\":[{\"scope\":\"masonic\",\"rank\":\"Companion\"}],\"EComp\":[{\"scope\":\"masonic\",\"rank\":\"MEZ\"},{\"scope\":\"masonic\",\"rank\":\"IPZ\"},{\"scope\":\"masonic\",\"rank\":\"PZ\"},{\"scope\":\"grand\",\"rank\":\"President of the Committee General Purposes\"},{\"scope\":\"grand\",\"rank\":\"Grand Registrar\"},{\"scope\":\"grand\",\"rank\":\"Grand Scribe Ezra\"},{\"scope\":\"grand\",\"rank\":\"Grand Scribe Nehemiah\"},{\"scope\":\"grand\",\"rank\":\"Grand Director of Cermonies\"},{\"scope\":\"grand\",\"rank\":\"Grand Sword Bearer\"},{\"scope\":\"grand\",\"rank\":\"Grand Inspector\"},{\"scope\":\"grand\",\"rank\":\"Grand Treasurer\"},{\"scope\":\"grand\",\"rank\":\"Deputy Grand Registrar\"},{\"scope\":\"grand\",\"rank\":\"Deputy Grand Scribe Ezra\"},{\"scope\":\"grand\",\"rank\":\"Deputy Grand Director of Ceremonies\"},{\"scope\":\"grand\",\"rank\":\"Deputy Grand Sword Bearer\"},{\"scope\":\"grand\",\"rank\":\"Principal Grand Sojourner\"},{\"scope\":\"grand\",\"rank\":\"First Assistant Grand Sojourner\"},{\"scope\":\"grand\",\"rank\":\"Second Assistant Grand Sojourner\"},{\"scope\":\"grand\",\"rank\":\"Assistant Grand Scribe Ezra\"},{\"scope\":\"grand\",\"rank\":\"Grand Standard Bearer\"},{\"scope\":\"grand\",\"rank\":\"Grand Organist\"},{\"scope\":\"grand\",\"rank\":\"Past Assistant Grand Director of Ceremonies\"},{\"scope\":\"grand\",\"rank\":\"Grand Janitor\"},{\"scope\":\"provincial\",\"rank\":\"Deputy Grand Superintendent\"},{\"scope\":\"provincial\",\"rank\":\"Second Provincial Grand Principal\"},{\"scope\":\"provincial\",\"rank\":\"Third Provincial Grand Principal\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Scribe Ezra\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Scribe Nehemiah\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Treasurer\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Registrar\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Director of Ceremonies\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Sword Bearer\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Deputy Grand Registrar\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Deputy Grand Scribe Ezra\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Deputy Grand Director of Ceremonies\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Assistant Grand Inspectors\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Almoner\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Charity Steward\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Membership Officer\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Communications Officer\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Sojourner\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Mentor\"},{\"scope\":\"provincial\",\"rank\":\"Provincial First Assistant Grand Sojourner\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Second Assistant Grand Sojourner\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Assistant Grand Scribe Ezra\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Standard Bearer\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Organist\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Assistant Grand Director of Ceremonies\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Janitor\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Steward\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Steward\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Janitor\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Assistant Grand Director of Ceremonies\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Organist\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Standard Bearer\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Assistant Grand Scribe Ezra\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Second Assistant Grand Sojourner\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial First Assistant Grand Sojourner\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Sojourner\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Mentor\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Communications Officer\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Membership Officer\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Charity Steward\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Almoner\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Assistant Grand Inspectors\"},{\"scope\":\"provincial\",\"rank\":\"Past Deputy Grand Superintendent\"},{\"scope\":\"provincial\",\"rank\":\"Past Second Provincial Grand Principal\"},{\"scope\":\"provincial\",\"rank\":\"Past Third Provincial Grand Principal\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Scribe Ezra\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Scribe Nehemiah\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Treasurer\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Registrar\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Director of Ceremonies\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Sword Bearer\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Deputy Grand Registrar\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Deputy Grand Scribe Ezra\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Deputy Grand Director of Ceremonies\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Janitor\"},{\"scope\":\"grand\",\"rank\":\"Past Past Assistant Grand Director of Ceremonies\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Organist\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Standard Bearer\"},{\"scope\":\"grand\",\"rank\":\"Past Assistant Grand Scribe Ezra\"},{\"scope\":\"grand\",\"rank\":\"Past Second Assistant Grand Sojourner\"},{\"scope\":\"grand\",\"rank\":\"Past First Assistant Grand Sojourner\"},{\"scope\":\"grand\",\"rank\":\"Past Principal Grand Sojourner\"},{\"scope\":\"grand\",\"rank\":\"Past Deputy Grand Sword Bearer\"},{\"scope\":\"grand\",\"rank\":\"Past Deputy Grand Director of Ceremonies\"},{\"scope\":\"grand\",\"rank\":\"Past Deputy Grand Scribe Ezra\"},{\"scope\":\"grand\",\"rank\":\"Past Deputy Grand Registrar\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Treasurer\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Inspector\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Sword Bearer\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Director of Cermonies\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Scribe Nehemiah\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Scribe Ezra\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Registrar\"},{\"scope\":\"grand\",\"rank\":\"Past President of the Committee General Purposes\"}],\"MEComp\":[{\"scope\":\"grand\",\"rank\":\"First Grand Principal\"},{\"scope\":\"grand\",\"rank\":\"Pro First Grand Principal\"},{\"scope\":\"grand\",\"rank\":\"Second Grand Principal\"},{\"scope\":\"grand\",\"rank\":\"Third Grand Principal\"},{\"scope\":\"grand\",\"rank\":\"Past First Grand Principal\"},{\"scope\":\"grand\",\"rank\":\"Past Pro First Grand Principal\"},{\"scope\":\"grand\",\"rank\":\"Past Second Grand Principal\"},{\"scope\":\"grand\",\"rank\":\"Past Third Grand Principal\"},{\"scope\":\"provincial\",\"rank\":\"Most Excellent Grand Superintendent\"},{\"scope\":\"provincial\",\"rank\":\"Past Most Excellent Grand Superintendent\"}]}','Companion\nMEZ\nIPZ\nPZ','Most Excellent Grand Superintendent\nDeputy Grand Superintendent\nSecond Provincial Grand Principal\nThird Provincial Grand Principal\nProvincial Grand Scribe Ezra\nProvincial Grand Scribe Nehemiah\nProvincial Grand Treasurer\nProvincial Grand Registrar\nProvincial Grand Director of Ceremonies\nProvincial Grand Sword Bearer\nProvincial Deputy Grand Registrar\nProvincial Deputy Grand Scribe Ezra\nProvincial Deputy Grand Director of Ceremonies\nProvincial Assistant Grand Inspectors\nProvincial Grand Almoner\nProvincial Grand Charity Steward\nProvincial Grand Membership Officer\nProvincial Grand Communications Officer\nProvincial Grand Mentor\nProvincial Grand Sojourner\nProvincial First Assistant Grand Sojourner\nProvincial Second Assistant Grand Sojourner\nProvincial Assistant Grand Scribe Ezra\nProvincial Grand Standard Bearer\nProvincial Grand Organist\nProvincial Assistant Grand Director of Ceremonies\nProvincial Grand Janitor\nProvincial Grand Steward','Past Most Excellent Grand Superintendent\nPast Deputy Grand Superintendent\nPast Second Provincial Grand Principal\nPast Third Provincial Grand Principal\nPast Provincial Grand Scribe Ezra\nPast Provincial Grand Scribe Nehemiah\nPast Provincial Grand Treasurer\nPast Provincial Grand Registrar\nPast Provincial Grand Director of Ceremonies\nPast Provincial Grand Sword Bearer\nPast Provincial Deputy Grand Registrar\nPast Provincial Deputy Grand Scribe Ezra\nPast Provincial Deputy Grand Director of Ceremonies\nPast Provincial Assistant Grand Inspectors\nPast Provincial Grand Almoner\nPast Provincial Grand Charity Steward\nPast Provincial Grand Membership Officer\nPast Provincial Grand Communications Officer\nPast Provincial Grand Mentor\nPast Provincial Grand Sojourner\nPast Provincial First Assistant Grand Sojourner\nPast Provincial Second Assistant Grand Sojourner\nPast Provincial Assistant Grand Scribe Ezra\nPast Provincial Grand Standard Bearer\nPast Provincial Grand Organist\nPast Provincial Assistant Grand Director of Ceremonies\nPast Provincial Grand Janitor\nPast Provincial Grand Steward','First Grand Principal\nPro First Grand Principal\nSecond Grand Principal\nThird Grand Principal\nPresident of the Committee General Purposes\nGrand Registrar\nGrand Scribe Ezra\nGrand Scribe Nehemiah\nGrand Director of Cermonies\nGrand Sword Bearer\nGrand Inspector\nGrand Treasurer\nDeputy Grand Registrar\nDeputy Grand Scribe Ezra\nDeputy Grand Director of Ceremonies\nDeputy Grand Sword Bearer\nPrincipal Grand Sojourner\nFirst Assistant Grand Sojourner\nSecond Assistant Grand Sojourner\nAssistant Grand Scribe Ezra\nGrand Standard Bearer\nGrand Organist\nPast Assistant Grand Director of Ceremonies\nGrand Janitor','Past First Grand Principal\nPast Pro First Grand Principal\nPast Second Grand Principal\nPast Third Grand Principal\nPast President of the Committee General Purposes\nPast Grand Registrar\nPast Grand Scribe Ezra\nPast Grand Scribe Nehemiah\nPast Grand Director of Cermonies\nPast Grand Sword Bearer\nPast Grand Inspector\nPast Grand Treasurer\nPast Deputy Grand Registrar\nPast Deputy Grand Scribe Ezra\nPast Deputy Grand Director of Ceremonies\nPast Deputy Grand Sword Bearer\nPast Principal Grand Sojourner\nPast First Assistant Grand Sojourner\nPast Second Assistant Grand Sojourner\nPast Assistant Grand Scribe Ezra\nPast Grand Standard Bearer\nPast Grand Organist\nPast Past Assistant Grand Director of Ceremonies\nPast Grand Janitor','2026-09-27 21:01:01'),('royal_ark_mariners','Bro\nWBro\nRWBro\nMWBro','{\"Bro\":[{\"scope\":\"masonic\",\"rank\":\"Royal Ark Mariner\"},{\"scope\":\"provincial\",\"rank\":\"Royal Ark Mariner Provincial Grand Rank\"},{\"scope\":\"provincial\",\"rank\":\"Past Royal Ark Mariner Provincial Grand Rank\"}],\"WBro\":[{\"scope\":\"grand\",\"rank\":\"Royal Ark Mariner Grand Rank\"},{\"scope\":\"grand\",\"rank\":\"Grand Master\'s Royal Ark Council Honoris Causa\"},{\"scope\":\"grand\",\"rank\":\"Member of the Grand Master\'s Royal Ark Council\"},{\"scope\":\"grand\",\"rank\":\"Past Royal Ark Mariner Grand Rank\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Master\'s Royal Ark Council Honoris Causa\"},{\"scope\":\"provincial\",\"rank\":\"Deputy Provincial Grand Master\"},{\"scope\":\"provincial\",\"rank\":\"Assistant Provincial Grand Master\"},{\"scope\":\"provincial\",\"rank\":\"Royal Ark Mariner Provincial Grand Rank\"},{\"scope\":\"provincial\",\"rank\":\"Past Deputy Provincial Grand Master\"},{\"scope\":\"provincial\",\"rank\":\"Past Assistant Provincial Grand Master\"},{\"scope\":\"provincial\",\"rank\":\"Past Royal Ark Mariner Provincial Grand Rank\"},{\"scope\":\"grand\",\"rank\":\"Past Member of the Grand Master\'s Royal Ark Council\"}],\"RWBro\":[{\"scope\":\"grand\",\"rank\":\"Deputy Grand Master\"},{\"scope\":\"grand\",\"rank\":\"Assistant Grand Master\"},{\"scope\":\"grand\",\"rank\":\"Member of the Grand Master\'s Royal Ark Council\"},{\"scope\":\"grand\",\"rank\":\"Past Deputy Grand Master\"},{\"scope\":\"grand\",\"rank\":\"Past Assistant Grand Master\"},{\"scope\":\"grand\",\"rank\":\"Past Member of the Grand Master\'s Royal Ark Council\"},{\"scope\":\"provincial\",\"rank\":\"Provincial Grand Master\"},{\"scope\":\"provincial\",\"rank\":\"Past Provincial Grand Master\"},{\"scope\":\"grand\",\"rank\":\"Grand Master\'s Royal Ark Council Honoris Causa\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Master\'s Royal Ark Council Honoris Causa\"}],\"MWBro\":[{\"scope\":\"grand\",\"rank\":\"Grand Master\"},{\"scope\":\"grand\",\"rank\":\"Pro Grand Master\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Master\"},{\"scope\":\"grand\",\"rank\":\"Past Pro Grand Master\"}]}','Royal Ark Mariner\nWorshipful Commander\nImmediate Past Commander\nPast Commander','Provincial Grand Master\nDeputy Provincial Grand Master\nAssistant Provincial Grand Master\nRoyal Ark Mariner Provincial Grand Rank','Past Provincial Grand Master\nPast Deputy Provincial Grand Master\nPast Assistant Provincial Grand Master\nPast Royal Ark Mariner Provincial Grand Rank','Grand Master\nPro Grand Master\nDeputy Grand Master\nAssistant Grand Master\nMember of the Grand Master\'s Royal Ark Council\nGrand Master\'s Royal Ark Council Honoris Causa\nRoyal Ark Mariner Grand Rank','Past Grand Master\nPast Pro Grand Master\nPast Deputy Grand Master\nPast Assistant Grand Master\nPast Member of the Grand Master\'s Royal Ark Council\nPast Grand Master\'s Royal Ark Council Honoris Causa\nPast Royal Ark Mariner Grand Rank','2026-09-27 21:01:01'),('royal_select','Comp\nIll.\nV.Ill\nR.Ill.\nM.Ill.','{\"Comp\":[{\"scope\":\"masonic\",\"rank\":\"Select Master\"},{\"scope\":\"masonic\",\"rank\":\"Royal Master\"},{\"scope\":\"masonic\",\"rank\":\"Most Excellent Master\"},{\"scope\":\"masonic\",\"rank\":\"Super-Excellent Master\"},{\"scope\":\"provincial\",\"rank\":\"District Grand Sentinel\"},{\"scope\":\"provincial\",\"rank\":\"District Grand Manciple\"},{\"scope\":\"provincial\",\"rank\":\"District Grand Steward\"},{\"scope\":\"provincial\",\"rank\":\"Past District Grand Steward\"},{\"scope\":\"provincial\",\"rank\":\"Past District Grand Manciple\"},{\"scope\":\"provincial\",\"rank\":\"Past District Grand Sentinel\"}],\"Ill.\":[{\"scope\":\"grand\",\"rank\":\"Deputy Grand Chaplain\"},{\"scope\":\"grand\",\"rank\":\"Grand Sentinel\"},{\"scope\":\"grand\",\"rank\":\"Grand Manciple\"},{\"scope\":\"grand\",\"rank\":\"Grand Steward\"},{\"scope\":\"grand\",\"rank\":\"Deputy Grand Organist\"},{\"scope\":\"grand\",\"rank\":\"Grand Organist\"},{\"scope\":\"grand\",\"rank\":\"Grand Sword Bearer\"},{\"scope\":\"grand\",\"rank\":\"Assistant Grand Director of Ceremonies\"},{\"scope\":\"grand\",\"rank\":\"Assistant Grand Recorder\"},{\"scope\":\"grand\",\"rank\":\"Grand Conductor of the Council\"},{\"scope\":\"grand\",\"rank\":\"Assistant Grand Chaplain\"},{\"scope\":\"grand\",\"rank\":\"Grand Captain of the Guard\"},{\"scope\":\"grand\",\"rank\":\"Deputy Grand Recorder\"},{\"scope\":\"grand\",\"rank\":\"Deputy Grand Director of Ceremonies\"},{\"scope\":\"grand\",\"rank\":\"Past Deputy Grand Chaplain\"},{\"scope\":\"grand\",\"rank\":\"Past Deputy Grand Recorder\"},{\"scope\":\"grand\",\"rank\":\"Past Deputy Grand Director of Ceremonies\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Captain of the Guard\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Conductor of the Council\"},{\"scope\":\"grand\",\"rank\":\"Past Assistant Grand Chaplain\"},{\"scope\":\"grand\",\"rank\":\"Past Assistant Grand Recorder\"},{\"scope\":\"grand\",\"rank\":\"Past Assistant Grand Director of Ceremonies\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Sword Bearer\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Organist\"},{\"scope\":\"grand\",\"rank\":\"Past Deputy Grand Organist\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Steward\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Manciple\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Sentinel\"},{\"scope\":\"provincial\",\"rank\":\"Deputy District Grand Master\"},{\"scope\":\"provincial\",\"rank\":\"District Principal Conductor of the Work\"},{\"scope\":\"provincial\",\"rank\":\"District Grand Chaplain\"},{\"scope\":\"provincial\",\"rank\":\"District Grand Recorder\"},{\"scope\":\"provincial\",\"rank\":\"District Grand Director of Ceremonies\"},{\"scope\":\"provincial\",\"rank\":\"District Grand Lecturer\"},{\"scope\":\"provincial\",\"rank\":\"District Deputy Grand Director of Ceremonies\"},{\"scope\":\"provincial\",\"rank\":\"District Grand Captain of the Guard\"},{\"scope\":\"provincial\",\"rank\":\"District Grand Almoner\"},{\"scope\":\"provincial\",\"rank\":\"District Assistant Grand Recorder\"},{\"scope\":\"provincial\",\"rank\":\"District Assistant Grand Director of Ceremonies\"},{\"scope\":\"provincial\",\"rank\":\"District Grand Sword Bearer\"},{\"scope\":\"provincial\",\"rank\":\"District Grand Standard Bearer\"},{\"scope\":\"provincial\",\"rank\":\"District Grand Organist\"},{\"scope\":\"provincial\",\"rank\":\"District Grand Steward\"},{\"scope\":\"provincial\",\"rank\":\"District Grand Manciple\"},{\"scope\":\"provincial\",\"rank\":\"District Grand Sentinel\"},{\"scope\":\"provincial\",\"rank\":\"Past District Grand Sentinel\"},{\"scope\":\"provincial\",\"rank\":\"Past District Grand Manciple\"},{\"scope\":\"provincial\",\"rank\":\"Past District Grand Steward\"},{\"scope\":\"provincial\",\"rank\":\"Past District Grand Organist\"},{\"scope\":\"provincial\",\"rank\":\"Past District Grand Standard Bearer\"},{\"scope\":\"provincial\",\"rank\":\"Past District Grand Sword Bearer\"},{\"scope\":\"provincial\",\"rank\":\"Past District Assistant Grand Director of Ceremonies\"},{\"scope\":\"provincial\",\"rank\":\"Past District Assistant Grand Recorder\"},{\"scope\":\"provincial\",\"rank\":\"Past District Grand Almoner\"},{\"scope\":\"provincial\",\"rank\":\"Past District Grand Captain of the Guard\"},{\"scope\":\"provincial\",\"rank\":\"Past District Deputy Grand Director of Ceremonies\"},{\"scope\":\"provincial\",\"rank\":\"Past District Grand Lecturer\"},{\"scope\":\"provincial\",\"rank\":\"Past District Grand Director of Ceremonies\"},{\"scope\":\"provincial\",\"rank\":\"Past District Grand Recorder\"},{\"scope\":\"provincial\",\"rank\":\"Past District Grand Chaplain\"},{\"scope\":\"provincial\",\"rank\":\"Past District Principal Conductor of the Work\"},{\"scope\":\"provincial\",\"rank\":\"Past Deputy District Grand Master\"}],\"V.Ill\":[{\"scope\":\"grand\",\"rank\":\"Grand Inspector\"},{\"scope\":\"grand\",\"rank\":\"Grand Historiographer\"},{\"scope\":\"grand\",\"rank\":\"Grand Chaplain\"},{\"scope\":\"grand\",\"rank\":\"Grand Treasurer\"},{\"scope\":\"grand\",\"rank\":\"President of the Executive Committee\"},{\"scope\":\"grand\",\"rank\":\"Grand Recorder\"},{\"scope\":\"grand\",\"rank\":\"Grand Director of Ceremonies\"},{\"scope\":\"grand\",\"rank\":\"Grand Lecturer\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Inspector\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Chaplain\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Treasurer\"},{\"scope\":\"grand\",\"rank\":\"Past President of the Executive Committee\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Recorder\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Director of Ceremonies\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Lecturer\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Historiographer\"}],\"R.Ill.\":[{\"scope\":\"grand\",\"rank\":\"Deputy Grand Master\"},{\"scope\":\"grand\",\"rank\":\"Grand Councillor\"},{\"scope\":\"grand\",\"rank\":\"Grand Principal Conductor of the Work\"},{\"scope\":\"grand\",\"rank\":\"Order of Service to Cryptic Masonry\"},{\"scope\":\"grand\",\"rank\":\"Custodian of the Ninth Arch\"},{\"scope\":\"grand\",\"rank\":\"Grand Chancellor\"},{\"scope\":\"grand\",\"rank\":\"Past Deputy Grand Master\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Principal Conductor of the Work\"},{\"scope\":\"grand\",\"rank\":\"Past Order of Service to Cryptic Masonry\"},{\"scope\":\"grand\",\"rank\":\"Past Custodian of the Ninth Arch\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Chancellor\"},{\"scope\":\"provincial\",\"rank\":\"District Grand Master\"},{\"scope\":\"provincial\",\"rank\":\"Past District Grand Master\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Councillor\"}],\"M.Ill.\":[{\"scope\":\"grand\",\"rank\":\"Grand Master\"},{\"scope\":\"grand\",\"rank\":\"Pro Grand Master\"},{\"scope\":\"grand\",\"rank\":\"Past Grand Master\"},{\"scope\":\"grand\",\"rank\":\"Past Pro Grand Master\"}]}','Select Master\nRoyal Master\nMost Excellent Master\nSuper-Excellent Master\nThrice Illustrious Master\nExcellent Master','District Grand Master\nDeputy District Grand Master\nDistrict Principal Conductor of the Work\nDistrict Grand Chaplain\nDistrict Grand Recorder\nDistrict Grand Director of Ceremonies\nDistrict Grand Lecturer\nDistrict Deputy Grand Director of Ceremonies\nDistrict Grand Captain of the Guard\nDistrict Grand Almoner\nDistrict Assistant Grand Recorder\nDistrict Assistant Grand Director of Ceremonies\nDistrict Grand Sword Bearer\nDistrict Grand Standard Bearer\nDistrict Grand Organist\nDistrict Grand Steward\nDistrict Grand Manciple\nDistrict Grand Sentinel','Past District Grand Master\nPast Deputy District Grand Master\nPast District Principal Conductor of the Work\nPast District Grand Chaplain\nPast District Grand Recorder\nPast District Grand Director of Ceremonies\nPast District Grand Lecturer\nPast District Deputy Grand Director of Ceremonies\nPast District Grand Captain of the Guard\nPast District Grand Almoner\nPast District Assistant Grand Recorder\nPast District Assistant Grand Director of Ceremonies\nPast District Grand Sword Bearer\nPast District Grand Standard Bearer\nPast District Grand Organist\nPast District Grand Steward\nPast District Grand Manciple\nPast District Grand Sentinel','Grand Master\nPro Grand Master\nDeputy Grand Master\nGrand Principal Conductor of the Work\nOrder of Service to Cryptic Masonry\nCustodian of the Ninth Arch\nGrand Chancellor\nGrand Councillor\nGrand Inspector\nGrand Chaplain\nGrand Treasurer\nPresident of the Executive Committee\nGrand Recorder\nGrand Director of Ceremonies\nGrand Lecturer\nGrand Historiographer\nDeputy Grand Chaplain\nDeputy Grand Recorder\nDeputy Grand Director of Ceremonies\nGrand Captain of the Guard\nGrand Conductor of the Council\nAssistant Grand Chaplain\nAssistant Grand Recorder\nAssistant Grand Director of Ceremonies\nGrand Sword Bearer\nGrand Organist\nDeputy Grand Organist\nGrand Steward\nGrand Manciple\nGrand Sentinel','Past Grand Master\nPast Pro Grand Master\nPast Deputy Grand Master\nPast Grand Principal Conductor of the Work\nPast Order of Service to Cryptic Masonry\nPast Custodian of the Ninth Arch\nPast Grand Chancellor\nPast Grand Councillor\nPast Grand Inspector\nPast Grand Chaplain\nPast Grand Treasurer\nPast President of the Executive Committee\nPast Grand Recorder\nPast Grand Director of Ceremonies\nPast Grand Lecturer\nPast Grand Historiographer\nPast Deputy Grand Chaplain\nPast Deputy Grand Recorder\nPast Deputy Grand Director of Ceremonies\nPast Grand Captain of the Guard\nPast Grand Conductor of the Council\nPast Assistant Grand Chaplain\nPast Assistant Grand Recorder\nPast Assistant Grand Director of Ceremonies\nPast Grand Sword Bearer\nPast Grand Organist\nPast Deputy Grand Organist\nPast Grand Steward\nPast Grand Manciple\nPast Grand Sentinel','2026-09-27 21:01:01');
/*!40000 ALTER TABLE `platform_order_rank_settings` ENABLE KEYS */;
UNLOCK TABLES;

LOCK TABLES `platform_email_templates` WRITE;
/*!40000 ALTER TABLE `platform_email_templates` DISABLE KEYS */;
INSERT INTO `platform_email_templates` (`template_key`, `display_name`, `subject_template`, `body_template`, `enabled`, `updated_at`) VALUES ('attendee_meeting_invitation','Previous attendee meeting invitation','{{lodge_name}} invitation — {{meeting_title}}','Dear {{name}},\n\nYou previously booked with the Lodge and agreed that it could retain your contact details and tell you when future meeting summonses or invitations are published.\n\nThe summons or invitation for {{meeting_title}} on {{meeting_date}} at {{meeting_time}} is now available.\n\nView it here: {{summons_url}}\nBook for this meeting: {{booking_url}}\n\nIf you no longer wish to receive these invitations, please contact the Lodge using the website contact form.',1,'2026-09-27 21:01:01'),('booking_cancelled','Booking cancelled','Booking cancelled – {{booking_reference}}','<p>Your booking for {{meeting_date}} has been cancelled.</p>',1,'2026-09-27 21:01:01'),('booking_code','Booking code','Your booking code – {{booking_reference}}','<p>Dear {{name}},</p><p>Your new booking code is <strong>{{booking_code}}</strong>.</p>',1,'2026-09-27 21:01:01'),('booking_confirmation','Booking confirmation','{{lodge_name}} booking confirmation – {{meeting_date}}','Booking confirmation\n\nDear {{name}},\n\nYour attendance has been recorded for {{meeting_date}}.\n\nReference: {{booking_reference}}\nBooking & Cancellation code: {{booking_code}}\n\nTotal due: {{total_charge}}\n\n{{payment_details}}\nPayment reference: {{booking_reference}}\n\nPlease keep your booking & cancellation code safe.',1,'2026-09-27 21:01:01'),('guest_meeting_invitation','Previous guest meeting invitation','{{lodge_name}} invitation — {{meeting_title}}','Dear {{name}},\n\nYou previously attended as a guest and agreed that the Lodge could retain your details and contact you about future meetings.\n\nThe summons for {{meeting_title}} on {{meeting_date}} at {{meeting_time}} is now available.\n\nView the summons: {{summons_url}}\nBook for this meeting: {{booking_url}}\n\nIf you no longer wish to receive these invitations, please contact the Lodge using the website contact form.',1,'2026-09-27 21:01:01'),('meeting_changed','Meeting details changed','Important update – {{meeting_date}}','<p>The meeting details have changed. Please review the updated information.</p>',1,'2026-09-27 21:01:01'),('meeting_reminder','Meeting reminder','Reminder – {{meeting_title}} on {{meeting_date}}','<p>Dear {{name}},</p><p>This is a reminder that {{meeting_title}} is on {{meeting_date}} at {{meeting_time}} at {{venue_name}}.</p>',1,'2026-09-27 21:01:01'),('payment_reminder','Payment reminder','Payment reminder – {{booking_reference}}','<p>Dear {{name}},</p><p>Our records show a balance of <strong>{{balance}}</strong> for {{meeting_date}}.</p>{{payment_details}}',1,'2026-09-27 21:01:01'),('place_confirmed','Waiting-list place confirmed','Your place is confirmed – {{booking_reference}}','<p>Dear {{name}},</p><p>Your place for {{meeting_title}} on {{meeting_date}} is now confirmed.</p>',1,'2026-09-27 21:01:01'),('place_offered','Waiting-list place offered','A dining place is available','<p>A place is available until {{offer_expiry}}. Please use your booking code to accept it.</p>',1,'2026-09-27 21:01:01'),('summons_email','Summons or invitation email','{{meeting_title}} – {{document_name}}','{{meeting_title}}\n\n{{meeting_date}} at {{meeting_time}}\n{{venue_name}}\n\nYour {{document_name}} is available here:\n{{document_url}}',1,'2026-09-27 21:01:01'),('waiting_list','Waiting list confirmation','Waiting list – {{meeting_date}}','<p>You have been added to the waiting list. We will contact you if a place becomes available.</p>',1,'2026-09-27 21:01:01');
/*!40000 ALTER TABLE `platform_email_templates` ENABLE KEYS */;
UNLOCK TABLES;

LOCK TABLES `unit_types` WRITE;
/*!40000 ALTER TABLE `unit_types` DISABLE KEYS */;
INSERT INTO `unit_types` (`id`, `type_key`, `display_name`, `body_noun`, `member_noun`, `built_in`, `active`, `created_at`, `updated_at`) VALUES (1,'lodge','Lodge','Lodge','Brother',1,1,'2026-09-27 21:01:01','2026-09-27 21:01:01'),(2,'chapter','Chapter','Chapter','Companion',1,1,'2026-09-27 21:01:01','2026-09-27 21:01:01'),(3,'council','Council','Council','Companion',1,1,'2026-09-27 21:01:01','2026-09-27 21:01:01'),(4,'other','Other Masonic Body','Unit','Member',1,1,'2026-09-27 21:01:01','2026-09-27 21:01:01');
/*!40000 ALTER TABLE `unit_types` ENABLE KEYS */;
UNLOCK TABLES;

LOCK TABLES `tenant_roles` WRITE;
/*!40000 ALTER TABLE `tenant_roles` DISABLE KEYS */;
INSERT INTO `tenant_roles` (`role_key`, `display_name`, `built_in`, `active`, `created_at`, `updated_at`) VALUES ('admin','Unit Administrator',1,1,'2026-09-27 21:01:01','2026-09-27 21:01:01'),('checkin_operator','Check-in Operator',1,1,'2026-09-27 21:01:01','2026-09-27 21:01:01'),('dining','Dining Steward',1,1,'2026-09-27 21:01:01','2026-09-27 21:01:01'),('document_viewer','Document Viewer',1,1,'2026-09-27 21:01:01','2026-09-27 21:01:01'),('member','Member',1,1,'2026-09-27 21:01:01','2026-09-27 21:01:01'),('platform_admin','Platform Administrator',1,1,'2026-09-27 21:01:01','2026-09-27 21:01:01'),('province_admin','Province Administrator',1,1,'2026-09-27 21:01:01','2026-09-27 21:01:01'),('province_secretary','Provincial Secretary',1,1,'2026-09-27 21:01:02','2026-09-27 21:01:02'),('readonly','Read-only Officer',1,1,'2026-09-27 21:01:01','2026-09-27 21:01:01'),('secretary','Secretary',1,1,'2026-09-27 21:01:01','2026-09-27 21:01:01'),('treasurer','Treasurer',1,1,'2026-09-27 21:01:01','2026-09-27 21:01:01'),('worshipful_master','Worshipful Master',1,1,'2026-09-27 21:01:01','2026-09-27 21:01:01');
/*!40000 ALTER TABLE `tenant_roles` ENABLE KEYS */;
UNLOCK TABLES;

LOCK TABLES `tenant_role_scopes` WRITE;
/*!40000 ALTER TABLE `tenant_role_scopes` DISABLE KEYS */;
INSERT INTO `tenant_role_scopes` (`role_key`, `scope_type`, `created_at`) VALUES ('admin','unit','2026-09-27 21:01:01'),('checkin_operator','unit','2026-09-27 21:01:01'),('dining','unit','2026-09-27 21:01:01'),('document_viewer','unit','2026-09-27 21:01:01'),('member','unit','2026-09-27 21:01:01'),('platform_admin','platform','2026-09-27 21:01:01'),('province_admin','province','2026-09-27 21:01:01'),('province_secretary','province','2026-09-27 21:01:02'),('readonly','unit','2026-09-27 21:01:01'),('secretary','unit','2026-09-27 21:01:01'),('treasurer','unit','2026-09-27 21:01:01'),('worshipful_master','unit','2026-09-27 21:01:01');
/*!40000 ALTER TABLE `tenant_role_scopes` ENABLE KEYS */;
UNLOCK TABLES;

LOCK TABLES `migration_history` WRITE;
/*!40000 ALTER TABLE `migration_history` DISABLE KEYS */;
INSERT INTO `migration_history` (`version`, `name`, `checksum`, `applied_at`, `execution_ms`) VALUES ('202609240001','Multi-Province and multi-unit security foundation','9c2133ea25711808e2e899bc37222a316bcca7c5e19664f3b1f8202d4c37431a','2026-09-27 21:01:01',0),('202609240002','Tenant catalogue, unit identifiers and membership role scopes','9106c46bcb26766f662c3c920426306898939c03739cb8eca0ca662353763a43','2026-09-27 21:01:01',0),('202609240003','Authentication rate limits','85e430feb2c8388e0ca6d3144f3c546085a4458792521524bb90aadf5878816d','2026-09-27 21:01:05',0),('202609240004','Scoped roles','ff1d3c871c97c7b954177975a2e27523bb267a63a5387e950c15027fd6ea123c','2026-09-27 21:01:05',0),('202609240005','Provincial administration','6d024f677c39a31e4a7c8fcfc773776d1c499f07f834079a920f337e2bd032d6','2026-09-27 21:01:05',0),('202609240006','Unit archive','8439fdea56e975062f70da1a01d9b42328329e6ed946c4740a0bc20bd8c3c1c6','2026-09-27 21:01:05',0),('202609240007','Province administrator invitations','7bdd92bb6f3b2cdaac81978d0c99f3af03b0684d96dcc5d3150807a4e214deb9','2026-09-27 21:01:05',0),('202609240008','Directional unit links','527089d1cf894def903200c6a305e4a7705860c43abe2ac45c9caeca1ef36803','2026-09-27 21:01:05',0),('202609240009','Tenant events','f6f6de76f1e22ffb0fb12154865ddf4ab692a43f91ca3e92e566adf5f233279f','2026-09-27 21:01:05',0),('202609240010','Event programmes','5fdf1b4a9c19259b772ec7128a7ff71dcb395aaaec3f70764eb539716c89d05c','2026-09-27 21:01:05',0),('202609240011','Public slug redirects','77a7a0c9a81d7e0b8815f1fe7bf67e09de9a727e8dd71f0fb7d21884ed6e38b4','2026-09-27 21:01:05',0),('202609240012','Guest directory consent','518869354375c9bb6db8d68c551aded10eda94043cc12c859dfc65db1d0183af','2026-09-27 21:01:05',0),('202609240013','Province secretary role','3f4129960e4d1c11b45cd163d712f70e71e987a18acb5bd84646475ba9d6300c','2026-09-27 21:01:05',0),('202609240014','Arrival and Province events','d965b1e0e25b50b6e5e72a5c10bd12dc71365d79643542fb222b55a233604a0d','2026-09-27 21:01:05',0),('202609240015','Province Order unique names','c81c2233ba9417d2698adbe132234120e0a29a342fc43ce4ae65be06af146a63','2026-09-27 21:01:05',0),('202609240016','Province Order charity logos','041032b506e5ca2a5974d7036f1a7afe40ac217d03052c82af7c1ddbd817d1af','2026-09-27 21:01:05',0),('202609250017','Tenant documents','49655857aafbefdc5a34e9725b2a0fc9e6122bb44876b4b82f0861d856ec358c','2026-09-27 21:01:05',0),('202609250018','Direct roles and unassigned units','b3c88f9db71ed544bf99c4f5b840b974ae0f485aa32fdd216df6fc186f0de36c','2026-09-27 21:01:05',0),('202609250019','Mail queue','2109156dc3ac203cb264b2b9634e14abc611cc3bce94ff0db6cddf23333ffd23','2026-09-27 21:01:05',0),('202609250020','Scheduled jobs','a7379359d5e1337b922162feb1d97020e25019ddc5da90d478b94f8b0a6e5c65','2026-09-27 21:01:05',0),('202609250021','Scoped reports and activity','cb8e4286c8395af5f06efb68f761c5002e94fbf94cb5cf90fe8e01bdc6247001','2026-09-27 21:01:05',0),('202609270022','Account MFA, session assurance and periodic scoped access reviews','e7dc2c6837312845915007d164d24bcb31f8153baae72d59bcf1b6d11b5e98bf','2026-09-27 21:01:04',0),('202609270023','Scoped permission sets and event grants','6a782f0f6c08e394f99af8fe1fd4685a626d3fa3737710e696249b7b43e8b078','2026-09-27 21:01:04',0),('202609270024','Unit members contacts candidates and reviewed identity resolution','491e0ad842af876c51f764aa5cda83bef99d67da58322f4fdfc1af991015d5a2','2026-09-27 21:01:05',0),('202609270025','Staged people CSV import and reconciliation','aea330de7f5acbd65696e132241e0587ce5a64b33f201958fdb55f6b6a5fd9c3','2026-09-27 21:01:05',0),('202609270026','Restricted data governance cases exports retention and suppression','54307884b267b3a404329a2651ef002855a2476585fc74d602c092afd3327cea','2026-09-27 21:01:05',0);
/*!40000 ALTER TABLE `migration_history` ENABLE KEYS */;
UNLOCK TABLES;

LOCK TABLES `schema_versions` WRITE;
/*!40000 ALTER TABLE `schema_versions` DISABLE KEYS */;
INSERT INTO `schema_versions` (`version_number`, `version_name`, `installed_at`) VALUES (22,'Operations and resilience centre','2026-09-27 21:01:00'),(26,'Consented guest directory and meeting invitations','2026-09-27 21:01:00'),(27,'Consented attendee directory and meeting invitations','2026-09-27 21:01:00'),(28,'Paying official visit classifications','2026-09-27 21:01:00');
/*!40000 ALTER TABLE `schema_versions` ENABLE KEYS */;
UNLOCK TABLES;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;

/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;



-- SOURCE: migrations/202609270027_member_hub_action_centre.sql
-- Explicit member relationships and role-owned action centre. No email-based access inference.
CREATE TABLE person_province_relationships (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, province_id BIGINT UNSIGNED NOT NULL, person_id BIGINT UNSIGNED NOT NULL,
 relationship_type VARCHAR(80) NOT NULL, status ENUM('active','inactive','ended') NOT NULL DEFAULT 'active',
 start_date DATE NULL, end_date DATE NULL, verified_by BIGINT UNSIGNED NOT NULL, verified_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 UNIQUE KEY uq_person_province_relationship(province_id,person_id,relationship_type),
 KEY idx_person_province(person_id,status,province_id),
 FOREIGN KEY(province_id) REFERENCES provinces(id) ON DELETE CASCADE,
 FOREIGN KEY(person_id) REFERENCES people(id) ON DELETE CASCADE,
 FOREIGN KEY(verified_by) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE member_booking_links (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, person_id BIGINT UNSIGNED NOT NULL, booking_id BIGINT UNSIGNED NOT NULL,
 verified_by BIGINT UNSIGNED NOT NULL, verified_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 UNIQUE KEY uq_member_booking(booking_id), KEY idx_member_booking_person(person_id,booking_id),
 FOREIGN KEY(person_id) REFERENCES people(id) ON DELETE CASCADE,
 FOREIGN KEY(booking_id) REFERENCES bookings(id) ON DELETE CASCADE,
 FOREIGN KEY(verified_by) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE member_visible_communications (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, person_id BIGINT UNSIGNED NOT NULL,
 unit_id BIGINT UNSIGNED NULL, province_id BIGINT UNSIGNED NULL, communication_id BIGINT UNSIGNED NULL,
 subject VARCHAR(250) NOT NULL, summary TEXT NULL, sent_at DATETIME NOT NULL,
 visible_from DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, revoked_at DATETIME NULL, linked_by BIGINT UNSIGNED NOT NULL,
 KEY idx_member_communication(person_id,unit_id,province_id,revoked_at,sent_at),
 FOREIGN KEY(person_id) REFERENCES people(id) ON DELETE CASCADE,
 FOREIGN KEY(unit_id) REFERENCES units(id) ON DELETE CASCADE,
 FOREIGN KEY(province_id) REFERENCES provinces(id) ON DELETE CASCADE,
 FOREIGN KEY(communication_id) REFERENCES communication_log(id) ON DELETE SET NULL,
 FOREIGN KEY(linked_by) REFERENCES users(id),
 CONSTRAINT chk_member_communication_scope CHECK((unit_id IS NULL)<>(province_id IS NULL))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE member_correction_requests (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, person_id BIGINT UNSIGNED NOT NULL,
 scope_type ENUM('profile','unit_membership','province_relationship') NOT NULL, scope_id BIGINT UNSIGNED NULL,
 field_key VARCHAR(60) NOT NULL, requested_value VARCHAR(500) NOT NULL, reason VARCHAR(1000) NULL,
 status ENUM('open','accepted','partially_accepted','declined','withdrawn') NOT NULL DEFAULT 'open',
 submitted_by BIGINT UNSIGNED NOT NULL, reviewed_by BIGINT UNSIGNED NULL, submitted_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, reviewed_at DATETIME NULL,
 KEY idx_member_correction(person_id,status,submitted_at),
 FOREIGN KEY(person_id) REFERENCES people(id) ON DELETE CASCADE,
 FOREIGN KEY(submitted_by) REFERENCES users(id), FOREIGN KEY(reviewed_by) REFERENCES users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE member_communication_preferences (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, person_id BIGINT UNSIGNED NOT NULL, scope_type ENUM('platform','province','unit') NOT NULL,
 province_id BIGINT UNSIGNED NULL, unit_id BIGINT UNSIGNED NULL,
 event_invitations TINYINT(1) NOT NULL DEFAULT 0, general_messages TINYINT(1) NOT NULL DEFAULT 0,
 service_messages TINYINT(1) NOT NULL DEFAULT 1, preferred_channel ENUM('email','phone','post','none') NOT NULL DEFAULT 'email',
 updated_by BIGINT UNSIGNED NOT NULL, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 scope_key VARCHAR(90) GENERATED ALWAYS AS (CONCAT(scope_type,':',IFNULL(province_id,0),':',IFNULL(unit_id,0))) STORED,
 UNIQUE KEY uq_member_preference_scope(person_id,scope_key),
 FOREIGN KEY(person_id) REFERENCES people(id) ON DELETE CASCADE,
 FOREIGN KEY(province_id) REFERENCES provinces(id) ON DELETE CASCADE,
 FOREIGN KEY(unit_id) REFERENCES units(id) ON DELETE CASCADE,
 FOREIGN KEY(updated_by) REFERENCES users(id),
 CONSTRAINT chk_member_preference_scope CHECK(
  (scope_type='platform' AND province_id IS NULL AND unit_id IS NULL) OR
  (scope_type='province' AND province_id IS NOT NULL AND unit_id IS NULL) OR
  (scope_type='unit' AND unit_id IS NOT NULL)
 )
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE action_centre_authorisations (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, unit_id BIGINT UNSIGNED NOT NULL,
 task_kind ENUM('unpaid_reminder') NOT NULL, authorised_by BIGINT UNSIGNED NOT NULL,
 active TINYINT(1) NOT NULL DEFAULT 1, expires_at DATETIME NULL, authorised_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 UNIQUE KEY uq_action_authorisation(unit_id,task_kind),
 FOREIGN KEY(unit_id) REFERENCES units(id) ON DELETE CASCADE, FOREIGN KEY(authorised_by) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE action_centre_tasks (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, unit_id BIGINT UNSIGNED NOT NULL,
 task_kind ENUM('role_message','invitation_request','booking_deadline','waiting_offer','draft_publication','unpaid_reminder','access_review') NOT NULL,
 source_type VARCHAR(50) NOT NULL, source_id BIGINT UNSIGNED NOT NULL, owner_role VARCHAR(50) NOT NULL,
 required_capability VARCHAR(80) NOT NULL, title VARCHAR(250) NOT NULL, due_at DATETIME NULL, action_url VARCHAR(500) NOT NULL,
 status ENUM('open','resolved','dismissed') NOT NULL DEFAULT 'open', resolved_at DATETIME NULL,
 created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 UNIQUE KEY uq_action_source(unit_id,task_kind,source_type,source_id), KEY idx_action_owner(unit_id,status,owner_role,due_at),
 FOREIGN KEY(unit_id) REFERENCES units(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE officer_handovers (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, unit_id BIGINT UNSIGNED NOT NULL, role_key VARCHAR(50) NOT NULL,
 outgoing_membership_id BIGINT UNSIGNED NOT NULL, incoming_membership_id BIGINT UNSIGNED NOT NULL,
 handover_at DATETIME NOT NULL, status ENUM('draft','active','completed','cancelled') NOT NULL DEFAULT 'draft',
 created_by BIGINT UNSIGNED NOT NULL, completed_by BIGINT UNSIGNED NULL,
 created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, completed_at DATETIME NULL,
 KEY idx_handover_unit(unit_id,status,handover_at),
 FOREIGN KEY(unit_id) REFERENCES units(id) ON DELETE CASCADE,
 FOREIGN KEY(outgoing_membership_id) REFERENCES user_memberships(id), FOREIGN KEY(incoming_membership_id) REFERENCES user_memberships(id),
 FOREIGN KEY(created_by) REFERENCES users(id), FOREIGN KEY(completed_by) REFERENCES users(id) ON DELETE SET NULL,
 CONSTRAINT chk_handover_memberships CHECK(outgoing_membership_id<>incoming_membership_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE officer_handover_items (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, handover_id BIGINT UNSIGNED NOT NULL,
 item_key VARCHAR(80) NOT NULL, item_label VARCHAR(250) NOT NULL, restricted_note TEXT NULL,
 outgoing_confirmed_at DATETIME NULL, incoming_confirmed_at DATETIME NULL,
 UNIQUE KEY uq_handover_item(handover_id,item_key),
 FOREIGN KEY(handover_id) REFERENCES officer_handovers(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

INSERT INTO migration_history(version,name,checksum,execution_ms)
VALUES('202609270027','Signed-in memberships hub, role action centre and officer handover',SHA2('202609270027_member_hub_action_centre_v1',256),0)
ON DUPLICATE KEY UPDATE name=VALUES(name),checksum=VALUES(checksum);


-- Scoped planning calendars. Bookable meetings remain canonical in meetings.
CREATE TABLE calendar_venues (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, province_id BIGINT UNSIGNED NOT NULL, unit_id BIGINT UNSIGNED NULL,
 name VARCHAR(200) NOT NULL, address VARCHAR(500) NULL, timezone VARCHAR(64) NOT NULL DEFAULT 'Europe/London', active TINYINT(1) NOT NULL DEFAULT 1,
 created_by BIGINT UNSIGNED NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 UNIQUE KEY uq_calendar_venue(unit_id,name), KEY idx_calendar_venue_province(province_id,active),
 FOREIGN KEY(province_id) REFERENCES provinces(id) ON DELETE CASCADE, FOREIGN KEY(unit_id) REFERENCES units(id) ON DELETE CASCADE,
 FOREIGN KEY(created_by) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE calendar_recurring_templates (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, scope_type ENUM('province','unit') NOT NULL,
 province_id BIGINT UNSIGNED NOT NULL, unit_id BIGINT UNSIGNED NULL,
 entry_kind ENUM('rehearsal','installation','provincial_visit','special_event','reporting_date','officer_task') NOT NULL,
 title VARCHAR(200) NOT NULL, description VARCHAR(500) NULL, order_key VARCHAR(40) NULL,
 venue_id BIGINT UNSIGNED NULL, timezone VARCHAR(64) NOT NULL DEFAULT 'Europe/London',
 local_start_time TIME NOT NULL, duration_minutes SMALLINT UNSIGNED NOT NULL DEFAULT 60,
 frequency ENUM('weekly','monthly','yearly') NOT NULL, recurrence_interval SMALLINT UNSIGNED NOT NULL DEFAULT 1,
 start_date DATE NOT NULL, until_date DATE NULL, occurrence_count SMALLINT UNSIGNED NULL,
 owner_user_id BIGINT UNSIGNED NULL, visibility ENUM('members','officers','private') NOT NULL DEFAULT 'officers', active TINYINT(1) NOT NULL DEFAULT 1,
 created_by BIGINT UNSIGNED NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 KEY idx_calendar_template_scope(scope_type,province_id,unit_id,active),
 FOREIGN KEY(province_id) REFERENCES provinces(id) ON DELETE CASCADE, FOREIGN KEY(unit_id) REFERENCES units(id) ON DELETE CASCADE,
 FOREIGN KEY(venue_id) REFERENCES calendar_venues(id) ON DELETE SET NULL, FOREIGN KEY(owner_user_id) REFERENCES users(id) ON DELETE SET NULL,
 FOREIGN KEY(created_by) REFERENCES users(id),
 CONSTRAINT chk_calendar_template_scope CHECK((scope_type='province' AND unit_id IS NULL) OR (scope_type='unit' AND unit_id IS NOT NULL))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE calendar_planning_entries (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, scope_type ENUM('province','unit') NOT NULL,
 province_id BIGINT UNSIGNED NOT NULL, unit_id BIGINT UNSIGNED NULL, template_id BIGINT UNSIGNED NULL,
 entry_kind ENUM('rehearsal','installation','provincial_visit','special_event','summons_deadline','dining_deadline','reporting_date','officer_task') NOT NULL,
 title VARCHAR(200) NOT NULL, description VARCHAR(500) NULL, order_key VARCHAR(40) NULL,
 venue_id BIGINT UNSIGNED NULL, venue_text VARCHAR(300) NULL, timezone VARCHAR(64) NOT NULL DEFAULT 'Europe/London',
 starts_at DATETIME NOT NULL, ends_at DATETIME NULL, task_due_at DATETIME NULL,
 owner_user_id BIGINT UNSIGNED NULL, visibility ENUM('members','officers','private') NOT NULL DEFAULT 'officers',
 status ENUM('draft','confirmed','cancelled','completed') NOT NULL DEFAULT 'draft', override_conflict TINYINT(1) NOT NULL DEFAULT 0,
 created_by BIGINT UNSIGNED NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 KEY idx_calendar_entry_scope(scope_type,province_id,unit_id,starts_at,status), KEY idx_calendar_owner(owner_user_id,task_due_at,status),
 FOREIGN KEY(province_id) REFERENCES provinces(id) ON DELETE CASCADE, FOREIGN KEY(unit_id) REFERENCES units(id) ON DELETE CASCADE,
 FOREIGN KEY(template_id) REFERENCES calendar_recurring_templates(id) ON DELETE SET NULL,
 FOREIGN KEY(venue_id) REFERENCES calendar_venues(id) ON DELETE SET NULL, FOREIGN KEY(owner_user_id) REFERENCES users(id) ON DELETE SET NULL,
 FOREIGN KEY(created_by) REFERENCES users(id),
 CONSTRAINT chk_calendar_entry_scope CHECK((scope_type='province' AND unit_id IS NULL) OR (scope_type='unit' AND unit_id IS NOT NULL))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE calendar_recurrence_exceptions (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, template_id BIGINT UNSIGNED NOT NULL, occurrence_date DATE NOT NULL,
 exception_kind ENUM('cancel','replace') NOT NULL, replacement_entry_id BIGINT UNSIGNED NULL, note VARCHAR(300) NULL,
 created_by BIGINT UNSIGNED NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 UNIQUE KEY uq_calendar_exception(template_id,occurrence_date),
 FOREIGN KEY(template_id) REFERENCES calendar_recurring_templates(id) ON DELETE CASCADE,
 FOREIGN KEY(replacement_entry_id) REFERENCES calendar_planning_entries(id) ON DELETE SET NULL, FOREIGN KEY(created_by) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE calendar_feed_tokens (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, user_id BIGINT UNSIGNED NOT NULL,
 scope_type ENUM('my','province','unit') NOT NULL, province_id BIGINT UNSIGNED NULL, unit_id BIGINT UNSIGNED NULL,
 token_hash CHAR(64) NOT NULL, label VARCHAR(100) NOT NULL, include_private TINYINT(1) NOT NULL DEFAULT 0,
 expires_at DATETIME NULL, revoked_at DATETIME NULL, last_used_at DATETIME NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 UNIQUE KEY uq_calendar_feed_token(token_hash), KEY idx_calendar_feed_user(user_id,revoked_at),
 FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY(province_id) REFERENCES provinces(id) ON DELETE CASCADE,
 FOREIGN KEY(unit_id) REFERENCES units(id) ON DELETE CASCADE,
 CONSTRAINT chk_calendar_feed_scope CHECK(
  (scope_type='my' AND province_id IS NULL AND unit_id IS NULL) OR
  (scope_type='province' AND province_id IS NOT NULL AND unit_id IS NULL) OR
  (scope_type='unit' AND unit_id IS NOT NULL)
 )
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE calendar_reminder_rules (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, entry_id BIGINT UNSIGNED NOT NULL, minutes_before INT UNSIGNED NOT NULL,
 recipient_user_id BIGINT UNSIGNED NOT NULL, active TINYINT(1) NOT NULL DEFAULT 1, last_queued_for DATETIME NULL,
 created_by BIGINT UNSIGNED NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 UNIQUE KEY uq_calendar_reminder(entry_id,minutes_before,recipient_user_id),
 FOREIGN KEY(entry_id) REFERENCES calendar_planning_entries(id) ON DELETE CASCADE,
 FOREIGN KEY(recipient_user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY(created_by) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

INSERT INTO migration_history(version,name,checksum,execution_ms)
VALUES('202609270028','Scoped unit Province and member calendars',SHA2('202609270028_scoped_calendars_v1',256),0)
ON DUPLICATE KEY UPDATE name=VALUES(name),checksum=VALUES(checksum);


-- Optional Director of Ceremonies workspace. Canonical meetings remain in meetings.
ALTER TABLE calendar_planning_entries MODIFY entry_kind ENUM('rehearsal','lodge_of_instruction','installation','provincial_visit','special_event','summons_deadline','dining_deadline','reporting_date','officer_task') NOT NULL;
ALTER TABLE calendar_recurring_templates MODIFY entry_kind ENUM('rehearsal','lodge_of_instruction','installation','provincial_visit','special_event','reporting_date','officer_task') NOT NULL;
ALTER TABLE tenant_retention_schedules MODIFY data_class ENUM('memberships','candidates','guest_directory','bookings','guests','messages','documents','welfare','finance','dc_private_notes') NOT NULL;

INSERT INTO tenant_roles(role_key,display_name,built_in,active) VALUES('director_of_ceremonies','Director of Ceremonies',1,1) ON DUPLICATE KEY UPDATE display_name=VALUES(display_name),active=1;
INSERT IGNORE INTO tenant_role_scopes(role_key,scope_type) VALUES('director_of_ceremonies','unit'),('director_of_ceremonies','province');

CREATE TABLE dc_ceremony_templates (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, scope_type ENUM('province','unit') NOT NULL, province_id BIGINT UNSIGNED NOT NULL, unit_id BIGINT UNSIGNED NULL,
 order_key VARCHAR(40) NOT NULL, title VARCHAR(200) NOT NULL, ceremony_term VARCHAR(100) NOT NULL, structure_json MEDIUMTEXT NOT NULL,
 active TINYINT(1) NOT NULL DEFAULT 1, created_by BIGINT UNSIGNED NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 KEY idx_dc_template_scope(scope_type,province_id,unit_id,order_key,active), FOREIGN KEY(province_id) REFERENCES provinces(id) ON DELETE CASCADE,
 FOREIGN KEY(unit_id) REFERENCES units(id) ON DELETE CASCADE, FOREIGN KEY(created_by) REFERENCES users(id),
 CONSTRAINT chk_dc_template_scope CHECK((scope_type='province' AND unit_id IS NULL) OR (scope_type='unit' AND unit_id IS NOT NULL))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE dc_template_roles (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, template_id BIGINT UNSIGNED NOT NULL, role_key VARCHAR(80) NOT NULL, role_label VARCHAR(150) NOT NULL,
 required TINYINT(1) NOT NULL DEFAULT 1, display_order SMALLINT UNSIGNED NOT NULL DEFAULT 0, instructions_cipher MEDIUMTEXT NULL,
 UNIQUE KEY uq_dc_template_role(template_id,role_key), FOREIGN KEY(template_id) REFERENCES dc_ceremony_templates(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE dc_event_programmes (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, meeting_id BIGINT UNSIGNED NOT NULL, unit_id BIGINT UNSIGNED NOT NULL, template_id BIGINT UNSIGNED NULL,
 title VARCHAR(200) NOT NULL, status ENUM('draft','approved','published','superseded') NOT NULL DEFAULT 'draft', version_no INT UNSIGNED NOT NULL DEFAULT 1,
 approved_by BIGINT UNSIGNED NULL, approved_at DATETIME NULL, published_at DATETIME NULL, created_by BIGINT UNSIGNED NOT NULL,
 created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 UNIQUE KEY uq_dc_programme_meeting(meeting_id), KEY idx_dc_programme_unit(unit_id,status), FOREIGN KEY(meeting_id) REFERENCES meetings(id) ON DELETE CASCADE,
 FOREIGN KEY(unit_id) REFERENCES units(id) ON DELETE CASCADE, FOREIGN KEY(template_id) REFERENCES dc_ceremony_templates(id) ON DELETE SET NULL,
 FOREIGN KEY(approved_by) REFERENCES users(id) ON DELETE SET NULL, FOREIGN KEY(created_by) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE dc_programme_assignments (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, programme_id BIGINT UNSIGNED NOT NULL, role_key VARCHAR(80) NOT NULL, role_label VARCHAR(150) NOT NULL,
 person_id BIGINT UNSIGNED NULL, understudy_person_id BIGINT UNSIGNED NULL, assignment_source ENUM('suggested','manual') NOT NULL DEFAULT 'manual',
 status ENUM('suggested','invited','accepted','declined','replaced','withdrawn') NOT NULL DEFAULT 'suggested', availability ENUM('unknown','available','unavailable') NOT NULL DEFAULT 'unknown',
 accepted_at DATETIME NULL, responded_at DATETIME NULL, updated_by BIGINT UNSIGNED NOT NULL, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 UNIQUE KEY uq_dc_programme_role(programme_id,role_key), KEY idx_dc_assignment_people(person_id,understudy_person_id,status),
 FOREIGN KEY(programme_id) REFERENCES dc_event_programmes(id) ON DELETE CASCADE, FOREIGN KEY(person_id) REFERENCES people(id) ON DELETE SET NULL,
 FOREIGN KEY(understudy_person_id) REFERENCES people(id) ON DELETE SET NULL, FOREIGN KEY(updated_by) REFERENCES users(id),
 CONSTRAINT chk_dc_different_understudy CHECK(person_id IS NULL OR understudy_person_id IS NULL OR person_id<>understudy_person_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE dc_assignment_changes (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, programme_id BIGINT UNSIGNED NOT NULL, version_no INT UNSIGNED NOT NULL, assignment_id BIGINT UNSIGNED NULL,
 change_kind ENUM('assigned','accepted','declined','replaced','withdrawn','approved','published') NOT NULL, before_json TEXT NULL, after_json TEXT NULL,
 changed_by BIGINT UNSIGNED NULL, changed_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 KEY idx_dc_change_version(programme_id,version_no,id), FOREIGN KEY(programme_id) REFERENCES dc_event_programmes(id) ON DELETE CASCADE,
 FOREIGN KEY(assignment_id) REFERENCES dc_programme_assignments(id) ON DELETE SET NULL, FOREIGN KEY(changed_by) REFERENCES users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE dc_rehearsals (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, programme_id BIGINT UNSIGNED NULL, calendar_entry_id BIGINT UNSIGNED NOT NULL, rehearsal_type ENUM('rehearsal','lodge_of_instruction') NOT NULL,
 created_by BIGINT UNSIGNED NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uq_dc_rehearsal_calendar(calendar_entry_id),
 FOREIGN KEY(programme_id) REFERENCES dc_event_programmes(id) ON DELETE SET NULL, FOREIGN KEY(calendar_entry_id) REFERENCES calendar_planning_entries(id) ON DELETE CASCADE,
 FOREIGN KEY(created_by) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE dc_rehearsal_attendance (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, rehearsal_id BIGINT UNSIGNED NOT NULL, person_id BIGINT UNSIGNED NOT NULL,
 attendance ENUM('invited','attended','apology','absent') NOT NULL DEFAULT 'invited', recorded_by BIGINT UNSIGNED NOT NULL, recorded_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 UNIQUE KEY uq_dc_rehearsal_person(rehearsal_id,person_id), FOREIGN KEY(rehearsal_id) REFERENCES dc_rehearsals(id) ON DELETE CASCADE,
 FOREIGN KEY(person_id) REFERENCES people(id) ON DELETE CASCADE, FOREIGN KEY(recorded_by) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE dc_volunteer_profiles (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, unit_id BIGINT UNSIGNED NOT NULL, person_id BIGINT UNSIGNED NOT NULL,
 preferred_roles_json TEXT NULL, availability_notes_cipher MEDIUMTEXT NULL, experience_cipher MEDIUMTEXT NULL, consented_at DATETIME NOT NULL,
 withdrawn_at DATETIME NULL, retention_until DATE NULL, updated_by BIGINT UNSIGNED NOT NULL, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 UNIQUE KEY uq_dc_volunteer(unit_id,person_id), KEY idx_dc_volunteer_active(unit_id,withdrawn_at,retention_until),
 FOREIGN KEY(unit_id) REFERENCES units(id) ON DELETE CASCADE, FOREIGN KEY(person_id) REFERENCES people(id) ON DELETE CASCADE, FOREIGN KEY(updated_by) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE dc_private_notes (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, unit_id BIGINT UNSIGNED NOT NULL, person_id BIGINT UNSIGNED NULL, programme_id BIGINT UNSIGNED NULL,
 note_cipher MEDIUMTEXT NOT NULL, retention_until DATE NOT NULL, created_by BIGINT UNSIGNED NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 deleted_at DATETIME NULL, KEY idx_dc_note_scope(unit_id,person_id,programme_id,retention_until), FOREIGN KEY(unit_id) REFERENCES units(id) ON DELETE CASCADE,
 FOREIGN KEY(person_id) REFERENCES people(id) ON DELETE SET NULL, FOREIGN KEY(programme_id) REFERENCES dc_event_programmes(id) ON DELETE CASCADE, FOREIGN KEY(created_by) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE dc_private_exports (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, unit_id BIGINT UNSIGNED NOT NULL, token_hash CHAR(64) NOT NULL, payload_cipher MEDIUMTEXT NOT NULL,
 expires_at DATETIME NOT NULL, created_by BIGINT UNSIGNED NOT NULL, downloaded_at DATETIME NULL, revoked_at DATETIME NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 UNIQUE KEY uq_dc_export_token(token_hash), KEY idx_dc_export_expiry(unit_id,expires_at,revoked_at), FOREIGN KEY(unit_id) REFERENCES units(id) ON DELETE CASCADE,
 FOREIGN KEY(created_by) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

INSERT INTO migration_history(version,name,checksum,execution_ms) VALUES('202609280029','Director of Ceremonies workspaces',SHA2('202609280029_director_of_ceremonies_v1',256),0)
ON DUPLICATE KEY UPDATE name=VALUES(name),checksum=VALUES(checksum);


-- Structured Province/unit file manager. Existing unit documents and versions remain in place.
CREATE TABLE document_folders (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, scope_type ENUM('province','unit') NOT NULL, province_id BIGINT UNSIGNED NOT NULL, unit_id BIGINT UNSIGNED NULL,
 stable_key VARCHAR(60) NOT NULL, display_label VARCHAR(100) NOT NULL, built_in TINYINT(1) NOT NULL DEFAULT 0, active TINYINT(1) NOT NULL DEFAULT 1,
 created_by BIGINT UNSIGNED NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 scope_key VARCHAR(90) GENERATED ALWAYS AS (CONCAT(scope_type,':',province_id,':',IFNULL(unit_id,0))) STORED,
 UNIQUE KEY uq_document_folder(scope_key,stable_key), KEY idx_document_folder_scope(scope_type,province_id,unit_id,active),
 FOREIGN KEY(province_id) REFERENCES provinces(id) ON DELETE CASCADE, FOREIGN KEY(unit_id) REFERENCES units(id) ON DELETE CASCADE,
 FOREIGN KEY(created_by) REFERENCES users(id) ON DELETE SET NULL,
 CONSTRAINT chk_document_folder_scope CHECK((scope_type='province' AND unit_id IS NULL) OR (scope_type='unit' AND unit_id IS NOT NULL))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

ALTER TABLE tenant_documents
 ADD COLUMN folder_id BIGINT UNSIGNED NULL AFTER custom_category_id,
 ADD COLUMN publication_state ENUM('private_officer','member','public_event') NOT NULL DEFAULT 'member' AFTER audience,
 ADD COLUMN redacted_from_document_id BIGINT UNSIGNED NULL AFTER publication_state,
 ADD COLUMN retention_review_at DATE NULL AFTER published_at,
 ADD COLUMN revoked_at DATETIME NULL AFTER archived_at,
 ADD KEY idx_tenant_document_public(unit_id,event_id,publication_state,published_at,revoked_at),
 ADD KEY fk_tenant_document_folder(folder_id), ADD KEY fk_tenant_document_redacted(redacted_from_document_id),
 ADD FOREIGN KEY(folder_id) REFERENCES document_folders(id) ON DELETE SET NULL,
 ADD FOREIGN KEY(redacted_from_document_id) REFERENCES tenant_documents(id) ON DELETE SET NULL;
UPDATE tenant_documents SET publication_state=CASE audience WHEN 'members' THEN 'member' ELSE 'private_officer' END;

CREATE TABLE province_documents (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, province_id BIGINT UNSIGNED NOT NULL, event_id BIGINT UNSIGNED NULL, folder_id BIGINT UNSIGNED NOT NULL,
 title VARCHAR(200) NOT NULL, description VARCHAR(500) NULL, publication_state ENUM('private_officer','member','public_event') NOT NULL DEFAULT 'private_officer',
 redacted_from_document_id BIGINT UNSIGNED NULL, published_at DATETIME NULL, retention_review_at DATE NULL, archived_at DATETIME NULL, revoked_at DATETIME NULL,
 deleted_at DATETIME NULL, created_by BIGINT UNSIGNED NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 UNIQUE KEY uq_province_document_id(province_id,id), KEY idx_province_document_scope(province_id,folder_id,event_id,publication_state,archived_at,revoked_at),
 FOREIGN KEY(province_id) REFERENCES provinces(id) ON DELETE CASCADE, FOREIGN KEY(event_id) REFERENCES meetings(id) ON DELETE SET NULL,
 FOREIGN KEY(folder_id) REFERENCES document_folders(id), FOREIGN KEY(redacted_from_document_id) REFERENCES province_documents(id) ON DELETE SET NULL,
 FOREIGN KEY(created_by) REFERENCES users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE province_document_versions (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, province_id BIGINT UNSIGNED NOT NULL, document_id BIGINT UNSIGNED NOT NULL, version_number INT UNSIGNED NOT NULL,
 storage_key CHAR(64) NOT NULL, original_filename VARCHAR(200) NOT NULL, mime_type VARCHAR(100) NOT NULL, byte_size INT UNSIGNED NOT NULL, sha256 CHAR(64) NOT NULL,
 uploaded_by BIGINT UNSIGNED NULL, uploaded_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 UNIQUE KEY uq_province_document_version(document_id,version_number), UNIQUE KEY uq_province_document_storage(storage_key),
 FOREIGN KEY(province_id,document_id) REFERENCES province_documents(province_id,id) ON DELETE CASCADE, FOREIGN KEY(uploaded_by) REFERENCES users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE document_storage_quotas (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, scope_type ENUM('province','unit') NOT NULL, province_id BIGINT UNSIGNED NOT NULL, unit_id BIGINT UNSIGNED NULL,
 quota_bytes BIGINT UNSIGNED NOT NULL DEFAULT 536870912, updated_by BIGINT UNSIGNED NULL, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 scope_key VARCHAR(90) GENERATED ALWAYS AS (CONCAT(scope_type,':',province_id,':',IFNULL(unit_id,0))) STORED, UNIQUE KEY uq_document_quota(scope_key),
 FOREIGN KEY(province_id) REFERENCES provinces(id) ON DELETE CASCADE, FOREIGN KEY(unit_id) REFERENCES units(id) ON DELETE CASCADE, FOREIGN KEY(updated_by) REFERENCES users(id) ON DELETE SET NULL,
 CONSTRAINT chk_document_quota_scope CHECK((scope_type='province' AND unit_id IS NULL) OR (scope_type='unit' AND unit_id IS NOT NULL))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

INSERT INTO document_folders(scope_type,province_id,unit_id,stable_key,display_label,built_in)
SELECT 'unit',u.province_id,u.id,x.stable_key,x.display_label,1 FROM units u JOIN (
 SELECT 'images' stable_key,'Images' display_label UNION ALL SELECT 'summons_invitations','Summons & Invitations' UNION ALL SELECT 'bylaws','Bylaws' UNION ALL
 SELECT 'minutes','Minutes' UNION ALL SELECT 'almoner_reports','Almoner Reports' UNION ALL SELECT 'treasurer_reports','Treasurer Reports' UNION ALL
 SELECT 'charity_steward_reports','Charity Steward Reports' UNION ALL SELECT 'general_documents','General Documents') x WHERE u.province_id IS NOT NULL;
INSERT INTO document_folders(scope_type,province_id,unit_id,stable_key,display_label,built_in)
SELECT 'province',p.id,NULL,x.stable_key,x.display_label,1 FROM provinces p JOIN (
 SELECT 'images' stable_key,'Images' display_label UNION ALL SELECT 'summons_invitations','Summons & Invitations' UNION ALL SELECT 'bylaws','Bylaws' UNION ALL
 SELECT 'minutes','Minutes' UNION ALL SELECT 'almoner_reports','Almoner Reports' UNION ALL SELECT 'treasurer_reports','Treasurer Reports' UNION ALL
 SELECT 'charity_steward_reports','Charity Steward Reports' UNION ALL SELECT 'general_documents','General Documents') x;
UPDATE tenant_documents d JOIN document_folders f ON f.scope_type='unit' AND f.unit_id=d.unit_id AND f.stable_key=CASE d.category_key WHEN 'summons' THEN 'summons_invitations' WHEN 'invitations' THEN 'summons_invitations' WHEN 'bylaws' THEN 'bylaws' WHEN 'minutes' THEN 'minutes' ELSE 'general_documents' END SET d.folder_id=f.id;

INSERT INTO migration_history(version,name,checksum,execution_ms) VALUES('202609280030','Structured Province and unit file manager',SHA2('202609280030_structured_file_manager_v1',256),0)
ON DUPLICATE KEY UPDATE name=VALUES(name),checksum=VALUES(checksum);


-- Unit and Province asset registers. Ownership is immutable; loans do not transfer it.
CREATE TABLE asset_register (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, scope_type ENUM('province','unit') NOT NULL, province_id BIGINT UNSIGNED NOT NULL, unit_id BIGINT UNSIGNED NULL,
 asset_identifier VARCHAR(80) NOT NULL, asset_type ENUM('jewel','collar','regalia','banner','furniture','key','other') NOT NULL,
 description VARCHAR(500) NOT NULL, condition_state ENUM('excellent','good','fair','poor','damaged','missing','disposed') NOT NULL DEFAULT 'good',
 location_label VARCHAR(200) NULL, acquisition_date DATE NULL, disposal_date DATE NULL, purchase_value DECIMAL(12,2) NULL, insured_value DECIMAL(12,2) NULL,
 key_details_cipher MEDIUMTEXT NULL, review_due_at DATE NULL, visibility ENUM('private_officer','member') NOT NULL DEFAULT 'private_officer',
 status ENUM('active','lost','disposal_pending','disposed','archived') NOT NULL DEFAULT 'active', created_by BIGINT UNSIGNED NOT NULL,
 created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 scope_key VARCHAR(90) GENERATED ALWAYS AS (CONCAT(scope_type,':',province_id,':',IFNULL(unit_id,0))) STORED,
 UNIQUE KEY uq_asset_identifier(scope_key,asset_identifier), KEY idx_asset_scope(scope_type,province_id,unit_id,status,review_due_at),
 FOREIGN KEY(province_id) REFERENCES provinces(id) ON DELETE CASCADE, FOREIGN KEY(unit_id) REFERENCES units(id) ON DELETE CASCADE, FOREIGN KEY(created_by) REFERENCES users(id),
 CONSTRAINT chk_asset_scope CHECK((scope_type='province' AND unit_id IS NULL) OR (scope_type='unit' AND unit_id IS NOT NULL)),
 CONSTRAINT chk_asset_disposal CHECK((status='disposed' AND disposal_date IS NOT NULL) OR status<>'disposed')
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE asset_images (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, asset_id BIGINT UNSIGNED NOT NULL, tenant_version_id BIGINT UNSIGNED NULL, province_version_id BIGINT UNSIGNED NULL,
 caption VARCHAR(200) NULL, created_by BIGINT UNSIGNED NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 UNIQUE KEY uq_asset_image_version(asset_id,tenant_version_id,province_version_id), FOREIGN KEY(asset_id) REFERENCES asset_register(id) ON DELETE CASCADE,
 FOREIGN KEY(tenant_version_id) REFERENCES tenant_document_versions(id) ON DELETE RESTRICT, FOREIGN KEY(province_version_id) REFERENCES province_document_versions(id) ON DELETE RESTRICT,
 FOREIGN KEY(created_by) REFERENCES users(id), CONSTRAINT chk_asset_image_version CHECK((tenant_version_id IS NULL)<>(province_version_id IS NULL))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE asset_assignments (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, asset_id BIGINT UNSIGNED NOT NULL, custodian_person_id BIGINT UNSIGNED NOT NULL,
 checked_out_at DATETIME NOT NULL, due_at DATETIME NULL, checked_in_at DATETIME NULL, issued_by BIGINT UNSIGNED NOT NULL, checked_in_by BIGINT UNSIGNED NULL,
 custodian_confirmed_at DATETIME NULL, return_confirmed_at DATETIME NULL, handover_note VARCHAR(500) NULL,
 active_asset_id BIGINT UNSIGNED GENERATED ALWAYS AS (IF(checked_in_at IS NULL,asset_id,NULL)) STORED,
 UNIQUE KEY uq_asset_active_assignment(active_asset_id), KEY idx_asset_custodian(custodian_person_id,checked_in_at,due_at),
 FOREIGN KEY(asset_id) REFERENCES asset_register(id) ON DELETE CASCADE, FOREIGN KEY(custodian_person_id) REFERENCES people(id),
 FOREIGN KEY(issued_by) REFERENCES users(id), FOREIGN KEY(checked_in_by) REFERENCES users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE asset_loans (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, asset_id BIGINT UNSIGNED NOT NULL, borrowing_unit_id BIGINT UNSIGNED NOT NULL,
 loaned_at DATETIME NOT NULL, due_at DATETIME NULL, returned_at DATETIME NULL, approved_by BIGINT UNSIGNED NOT NULL, received_by BIGINT UNSIGNED NULL,
 active_asset_id BIGINT UNSIGNED GENERATED ALWAYS AS (IF(returned_at IS NULL,asset_id,NULL)) STORED,
 UNIQUE KEY uq_asset_active_loan(active_asset_id), KEY idx_asset_loan_unit(borrowing_unit_id,returned_at,due_at),
 FOREIGN KEY(asset_id) REFERENCES asset_register(id) ON DELETE CASCADE, FOREIGN KEY(borrowing_unit_id) REFERENCES units(id),
 FOREIGN KEY(approved_by) REFERENCES users(id), FOREIGN KEY(received_by) REFERENCES users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE asset_history (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, asset_id BIGINT UNSIGNED NOT NULL, event_type ENUM('created','updated','image_linked','checked_out','custodian_confirmed','checked_in','return_confirmed','loaned','loan_returned','lost','found','disposal_requested','disposed','archived','published','unpublished') NOT NULL,
 assignment_id BIGINT UNSIGNED NULL, loan_id BIGINT UNSIGNED NULL, change_json TEXT NULL, actor_user_id BIGINT UNSIGNED NULL, occurred_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 KEY idx_asset_history(asset_id,occurred_at,id), FOREIGN KEY(asset_id) REFERENCES asset_register(id) ON DELETE CASCADE,
 FOREIGN KEY(assignment_id) REFERENCES asset_assignments(id) ON DELETE SET NULL, FOREIGN KEY(loan_id) REFERENCES asset_loans(id) ON DELETE SET NULL,
 FOREIGN KEY(actor_user_id) REFERENCES users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Custody evidence is append-only. Corrections are recorded as a later event.
CREATE TRIGGER asset_history_no_update BEFORE UPDATE ON asset_history FOR EACH ROW
 SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Asset history is append-only';
CREATE TRIGGER asset_history_no_delete BEFORE DELETE ON asset_history FOR EACH ROW
 SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Asset history is append-only';

INSERT INTO migration_history(version,name,checksum,execution_ms) VALUES('202609280031','Unit and Province asset registers',SHA2('202609280031_asset_registers_v2',256),0)
ON DUPLICATE KEY UPDATE name=VALUES(name),checksum=VALUES(checksum);


-- Structured agendas, immutable summons/minutes publications, sign-off and actions.
CREATE TABLE meeting_agenda_templates (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, scope_type ENUM('province','unit') NOT NULL, province_id BIGINT UNSIGNED NOT NULL, unit_id BIGINT UNSIGNED NULL,
 name VARCHAR(160) NOT NULL, order_key VARCHAR(40) NOT NULL DEFAULT 'craft', active TINYINT(1) NOT NULL DEFAULT 1, current_version INT UNSIGNED NOT NULL DEFAULT 1,
 created_by BIGINT UNSIGNED NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 scope_key VARCHAR(90) GENERATED ALWAYS AS (CONCAT(scope_type,':',province_id,':',IFNULL(unit_id,0))) STORED,
 UNIQUE KEY uq_agenda_template(scope_key,name), FOREIGN KEY(province_id) REFERENCES provinces(id) ON DELETE CASCADE, FOREIGN KEY(unit_id) REFERENCES units(id) ON DELETE CASCADE, FOREIGN KEY(created_by) REFERENCES users(id),
 CONSTRAINT chk_agenda_template_scope CHECK((scope_type='province' AND unit_id IS NULL) OR (scope_type='unit' AND unit_id IS NOT NULL))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE meeting_agenda_template_items (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, template_id BIGINT UNSIGNED NOT NULL, version_no INT UNSIGNED NOT NULL, item_no INT UNSIGNED NOT NULL,
 heading VARCHAR(220) NOT NULL, item_type ENUM('opening','minutes','matters','ceremony','motion','report','business','closing','other') NOT NULL DEFAULT 'business',
 responsible_role VARCHAR(120) NULL, resolution_placeholder TEXT NULL, sensitivity ENUM('general','committee','welfare','finance') NOT NULL DEFAULT 'general', created_by BIGINT UNSIGNED NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 UNIQUE KEY uq_agenda_template_item(template_id,version_no,item_no), FOREIGN KEY(template_id) REFERENCES meeting_agenda_templates(id) ON DELETE CASCADE, FOREIGN KEY(created_by) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE meeting_agendas (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, meeting_id BIGINT UNSIGNED NOT NULL, scope_type ENUM('province','unit') NOT NULL, province_id BIGINT UNSIGNED NOT NULL, unit_id BIGINT UNSIGNED NOT NULL,
 template_id BIGINT UNSIGNED NULL, title VARCHAR(220) NOT NULL, status ENUM('draft','review','approved','superseded','archived') NOT NULL DEFAULT 'draft', version_no INT UNSIGNED NOT NULL DEFAULT 1,
 approved_by BIGINT UNSIGNED NULL, approved_at DATETIME NULL, created_by BIGINT UNSIGNED NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 UNIQUE KEY uq_meeting_agenda_scope(meeting_id,scope_type,province_id,unit_id), KEY idx_agenda_scope(scope_type,province_id,unit_id,status),
 FOREIGN KEY(meeting_id) REFERENCES meetings(id) ON DELETE CASCADE, FOREIGN KEY(province_id) REFERENCES provinces(id) ON DELETE CASCADE, FOREIGN KEY(unit_id) REFERENCES units(id) ON DELETE CASCADE,
 FOREIGN KEY(template_id) REFERENCES meeting_agenda_templates(id) ON DELETE SET NULL, FOREIGN KEY(approved_by) REFERENCES users(id) ON DELETE SET NULL, FOREIGN KEY(created_by) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE meeting_agenda_items (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, agenda_id BIGINT UNSIGNED NOT NULL, logical_key CHAR(32) NOT NULL, version_no INT UNSIGNED NOT NULL, item_no INT UNSIGNED NOT NULL,
 heading VARCHAR(220) NOT NULL, body_text TEXT NULL, proposer_person_id BIGINT UNSIGNED NULL, responsible_role VARCHAR(120) NULL, resolution_text TEXT NULL,
 sensitivity ENUM('general','committee','welfare','finance') NOT NULL DEFAULT 'general', approved_for_general TINYINT(1) NOT NULL DEFAULT 0, item_status ENUM('draft','approved','withdrawn') NOT NULL DEFAULT 'draft',
 created_by BIGINT UNSIGNED NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 UNIQUE KEY uq_agenda_item_version(agenda_id,logical_key,version_no), KEY idx_agenda_items(agenda_id,version_no,item_no),
 FOREIGN KEY(agenda_id) REFERENCES meeting_agendas(id) ON DELETE CASCADE, FOREIGN KEY(proposer_person_id) REFERENCES people(id) ON DELETE SET NULL, FOREIGN KEY(created_by) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE meeting_publication_snapshots (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, agenda_id BIGINT UNSIGNED NOT NULL, meeting_id BIGINT UNSIGNED NOT NULL,
 publication_type ENUM('summons','minutes','minutes_correction','minutes_addendum') NOT NULL, publication_no INT UNSIGNED NOT NULL, amendment_no INT UNSIGNED NOT NULL DEFAULT 0,
 parent_snapshot_id BIGINT UNSIGNED NULL, status ENUM('approved','issued','review','confirmed','locked','archived') NOT NULL, title VARCHAR(250) NOT NULL,
 event_fingerprint CHAR(64) NOT NULL, content_json LONGTEXT NOT NULL, document_id BIGINT UNSIGNED NULL, province_document_id BIGINT UNSIGNED NULL, document_version_id BIGINT UNSIGNED NULL, province_document_version_id BIGINT UNSIGNED NULL,
 approved_by BIGINT UNSIGNED NOT NULL, approved_at DATETIME NOT NULL, issued_at DATETIME NULL, confirmed_at DATETIME NULL, confirmed_meeting_id BIGINT UNSIGNED NULL, signed_off_by BIGINT UNSIGNED NULL, signed_off_at DATETIME NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 UNIQUE KEY uq_publication_number(agenda_id,publication_type,publication_no,amendment_no), KEY idx_publication_meeting(meeting_id,publication_type,status),
 FOREIGN KEY(agenda_id) REFERENCES meeting_agendas(id) ON DELETE CASCADE, FOREIGN KEY(meeting_id) REFERENCES meetings(id) ON DELETE CASCADE, FOREIGN KEY(parent_snapshot_id) REFERENCES meeting_publication_snapshots(id) ON DELETE RESTRICT,
 FOREIGN KEY(document_id) REFERENCES tenant_documents(id) ON DELETE SET NULL, FOREIGN KEY(province_document_id) REFERENCES province_documents(id) ON DELETE SET NULL, FOREIGN KEY(document_version_id) REFERENCES tenant_document_versions(id) ON DELETE SET NULL, FOREIGN KEY(province_document_version_id) REFERENCES province_document_versions(id) ON DELETE SET NULL,
 FOREIGN KEY(approved_by) REFERENCES users(id), FOREIGN KEY(confirmed_meeting_id) REFERENCES meetings(id) ON DELETE SET NULL, FOREIGN KEY(signed_off_by) REFERENCES users(id) ON DELETE SET NULL,
 CONSTRAINT chk_publication_document CHECK((document_id IS NULL) OR (province_document_id IS NULL))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE meeting_publication_recipients (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, snapshot_id BIGINT UNSIGNED NOT NULL, person_id BIGINT UNSIGNED NULL, recipient_email VARCHAR(254) NOT NULL, audience_source ENUM('unit_member','province_relationship','booking_attendee') NOT NULL,
 mail_queue_id BIGINT UNSIGNED NULL, queued_at DATETIME NULL, sent_at DATETIME NULL, delivery_status ENUM('pending','queued','sent','failed','suppressed') NOT NULL DEFAULT 'pending',
 UNIQUE KEY uq_publication_recipient(snapshot_id,recipient_email), FOREIGN KEY(snapshot_id) REFERENCES meeting_publication_snapshots(id) ON DELETE CASCADE, FOREIGN KEY(person_id) REFERENCES people(id) ON DELETE SET NULL, FOREIGN KEY(mail_queue_id) REFERENCES outbound_mail_queue(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE meeting_minutes_actions (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, snapshot_id BIGINT UNSIGNED NOT NULL, agenda_item_key CHAR(32) NULL, title VARCHAR(250) NOT NULL, responsible_role VARCHAR(80) NOT NULL, due_at DATETIME NULL,
 status ENUM('open','completed','cancelled') NOT NULL DEFAULT 'open', action_task_id BIGINT UNSIGNED NULL, created_by BIGINT UNSIGNED NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, completed_at DATETIME NULL,
 KEY idx_minutes_action(snapshot_id,status,due_at), FOREIGN KEY(snapshot_id) REFERENCES meeting_publication_snapshots(id) ON DELETE CASCADE, FOREIGN KEY(action_task_id) REFERENCES action_centre_tasks(id) ON DELETE SET NULL, FOREIGN KEY(created_by) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

ALTER TABLE action_centre_tasks MODIFY task_kind ENUM('role_message','invitation_request','booking_deadline','waiting_offer','draft_publication','unpaid_reminder','access_review','minutes_action') NOT NULL;
ALTER TABLE outbound_mail_queue ADD COLUMN province_id BIGINT UNSIGNED NULL AFTER unit_id, ADD KEY idx_mail_province(province_id,status), ADD CONSTRAINT fk_mail_province FOREIGN KEY(province_id) REFERENCES provinces(id) ON DELETE SET NULL;

CREATE TRIGGER meeting_publication_no_update_locked BEFORE UPDATE ON meeting_publication_snapshots FOR EACH ROW
 SET NEW.status=IF(OLD.status='locked','locked',NEW.status), NEW.content_json=IF(OLD.status='locked',OLD.content_json,NEW.content_json), NEW.event_fingerprint=IF(OLD.status='locked',OLD.event_fingerprint,NEW.event_fingerprint);
CREATE TRIGGER meeting_publication_no_delete BEFORE DELETE ON meeting_publication_snapshots FOR EACH ROW
 SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Publication snapshots are append-only';

INSERT INTO migration_history(version,name,checksum,execution_ms) VALUES('202609280032','Structured agendas, summons and minutes',SHA2('202609280032_agendas_summons_minutes_v2',256),0)
ON DUPLICATE KEY UPDATE name=VALUES(name),checksum=VALUES(checksum);


-- Per-unit delivery preferences and snapshot-derived postal dispatch queue.
CREATE TABLE unit_member_delivery_preferences (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, unit_id BIGINT UNSIGNED NOT NULL, person_id BIGINT UNSIGNED NOT NULL,
 essential_delivery ENUM('email','post','both','none') NOT NULL DEFAULT 'email', optional_delivery ENUM('email','post','both','none') NOT NULL DEFAULT 'none',
 postal_address_cipher MEDIUMTEXT NULL, postal_address_source ENUM('member_provided','officer_verified','imported','historic_record') NULL,
 postal_verified_at DATETIME NULL, postal_verified_by BIGINT UNSIGNED NULL, updated_by BIGINT UNSIGNED NOT NULL,
 created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 UNIQUE KEY uq_unit_member_delivery(unit_id,person_id), KEY idx_delivery_person(person_id,unit_id),
 FOREIGN KEY(unit_id) REFERENCES units(id) ON DELETE CASCADE, FOREIGN KEY(person_id) REFERENCES people(id) ON DELETE CASCADE,
 FOREIGN KEY(postal_verified_by) REFERENCES users(id) ON DELETE SET NULL, FOREIGN KEY(updated_by) REFERENCES users(id),
 CONSTRAINT chk_postal_verification CHECK((postal_address_cipher IS NULL AND postal_address_source IS NULL AND postal_verified_at IS NULL) OR (postal_address_cipher IS NOT NULL AND postal_address_source IS NOT NULL AND postal_verified_at IS NOT NULL))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

ALTER TABLE meeting_publication_recipients
 DROP INDEX uq_publication_recipient,
 MODIFY recipient_email VARCHAR(254) NULL,
 ADD COLUMN notice_class ENUM('essential','optional') NOT NULL DEFAULT 'essential' AFTER audience_source,
 ADD COLUMN delivery_method ENUM('email','post','both','none') NOT NULL DEFAULT 'none' AFTER notice_class,
 ADD COLUMN email_eligible TINYINT(1) NOT NULL DEFAULT 0 AFTER delivery_method,
 ADD COLUMN postal_eligible TINYINT(1) NOT NULL DEFAULT 0 AFTER email_eligible,
 ADD COLUMN postal_address_cipher MEDIUMTEXT NULL AFTER postal_eligible,
 ADD COLUMN postal_address_source VARCHAR(40) NULL AFTER postal_address_cipher,
 ADD COLUMN postal_verified_at DATETIME NULL AFTER postal_address_source,
 ADD COLUMN delivery_issue ENUM('missing_email','missing_address','preference_none') NULL AFTER postal_verified_at,
 ADD UNIQUE KEY uq_publication_person(snapshot_id,person_id);

CREATE TABLE postal_dispatches (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, recipient_id BIGINT UNSIGNED NOT NULL, snapshot_id BIGINT UNSIGNED NOT NULL,
 status ENUM('ready','exported','posted','returned','undeliverable','cancelled') NOT NULL DEFAULT 'ready',
 exported_at DATETIME NULL, posted_at DATETIME NULL, returned_at DATETIME NULL, status_note VARCHAR(500) NULL,
 updated_by BIGINT UNSIGNED NULL, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 UNIQUE KEY uq_postal_dispatch_recipient(recipient_id), KEY idx_postal_queue(snapshot_id,status),
 FOREIGN KEY(recipient_id) REFERENCES meeting_publication_recipients(id) ON DELETE CASCADE, FOREIGN KEY(snapshot_id) REFERENCES meeting_publication_snapshots(id) ON DELETE CASCADE,
 FOREIGN KEY(updated_by) REFERENCES users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE postal_address_exports (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, unit_id BIGINT UNSIGNED NOT NULL, snapshot_id BIGINT UNSIGNED NOT NULL,
 token_hash CHAR(64) NOT NULL, export_kind ENUM('labels','letters') NOT NULL, payload_cipher LONGTEXT NOT NULL,
 expires_at DATETIME NOT NULL, downloaded_at DATETIME NULL, revoked_at DATETIME NULL, created_by BIGINT UNSIGNED NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 UNIQUE KEY uq_postal_export_token(token_hash), KEY idx_postal_export(unit_id,snapshot_id,expires_at),
 FOREIGN KEY(unit_id) REFERENCES units(id) ON DELETE CASCADE, FOREIGN KEY(snapshot_id) REFERENCES meeting_publication_snapshots(id) ON DELETE CASCADE, FOREIGN KEY(created_by) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE postal_dispatch_history (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, dispatch_id BIGINT UNSIGNED NOT NULL, from_status VARCHAR(30) NULL, to_status VARCHAR(30) NOT NULL,
 note VARCHAR(500) NULL, actor_user_id BIGINT UNSIGNED NOT NULL, occurred_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 KEY idx_postal_history(dispatch_id,occurred_at,id), FOREIGN KEY(dispatch_id) REFERENCES postal_dispatches(id) ON DELETE CASCADE, FOREIGN KEY(actor_user_id) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TRIGGER postal_dispatch_history_no_update BEFORE UPDATE ON postal_dispatch_history FOR EACH ROW SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Postal history is append-only';
CREATE TRIGGER postal_dispatch_history_no_delete BEFORE DELETE ON postal_dispatch_history FOR EACH ROW SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Postal history is append-only';

INSERT INTO migration_history(version,name,checksum,execution_ms) VALUES('202609280033','Per-unit postal delivery and print queue',SHA2('202609280033_postal_delivery_queue_v1',256),0)
ON DUPLICATE KEY UPDATE name=VALUES(name),checksum=VALUES(checksum);


-- Versioned annual and installation returns.

CREATE TABLE return_template_versions (
  id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  province_id BIGINT UNSIGNED NOT NULL,
  order_key VARCHAR(40) NOT NULL,
  return_type ENUM('annual','installation') NOT NULL,
  version_no INT UNSIGNED NOT NULL,
  name VARCHAR(180) NOT NULL,
  terminology_json JSON NOT NULL,
  field_config_json JSON NOT NULL,
  supports_pdf TINYINT(1) NOT NULL DEFAULT 0,
  supports_csv TINYINT(1) NOT NULL DEFAULT 1,
  supports_xlsx TINYINT(1) NOT NULL DEFAULT 0,
  requirements_verified_at DATETIME NULL,
  requirements_source VARCHAR(500) NULL,
  effective_from DATE NULL,
  effective_to DATE NULL,
  active TINYINT(1) NOT NULL DEFAULT 1,
  created_by BIGINT UNSIGNED NOT NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  UNIQUE KEY uq_return_template_version(province_id,order_key,return_type,version_no),
  KEY idx_return_template_lookup(province_id,order_key,return_type,active,effective_from),
  CONSTRAINT fk_return_template_province FOREIGN KEY(province_id) REFERENCES provinces(id),
  CONSTRAINT fk_return_template_actor FOREIGN KEY(created_by) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE unit_returns (
  id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  unit_id BIGINT UNSIGNED NOT NULL,
  province_id BIGINT UNSIGNED NOT NULL,
  template_version_id BIGINT UNSIGNED NOT NULL,
  parent_return_id BIGINT UNSIGNED NULL,
  amendment_no INT UNSIGNED NOT NULL DEFAULT 0,
  return_type ENUM('annual','installation') NOT NULL,
  order_key VARCHAR(40) NOT NULL,
  period_start DATE NOT NULL,
  period_end DATE NOT NULL,
  installation_date DATE NULL,
  status ENUM('draft','review','signed','locked','superseded') NOT NULL DEFAULT 'draft',
  revision_no INT UNSIGNED NOT NULL DEFAULT 1,
  working_payload_json JSON NOT NULL,
  source_fingerprint CHAR(64) NOT NULL,
  missing_flags_json JSON NOT NULL,
  validation_json JSON NOT NULL,
  created_by BIGINT UNSIGNED NOT NULL,
  reviewed_by BIGINT UNSIGNED NULL,
  reviewed_at DATETIME NULL,
  signed_by BIGINT UNSIGNED NULL,
  signed_at DATETIME NULL,
  locked_at DATETIME NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  UNIQUE KEY uq_unit_return_amendment(unit_id,return_type,order_key,period_start,period_end,amendment_no),
  KEY idx_unit_return_province(province_id,status,period_end),
  CONSTRAINT fk_unit_return_unit FOREIGN KEY(unit_id) REFERENCES units(id),
  CONSTRAINT fk_unit_return_province FOREIGN KEY(province_id) REFERENCES provinces(id),
  CONSTRAINT fk_unit_return_template FOREIGN KEY(template_version_id) REFERENCES return_template_versions(id),
  CONSTRAINT fk_unit_return_parent FOREIGN KEY(parent_return_id) REFERENCES unit_returns(id),
  CONSTRAINT fk_unit_return_creator FOREIGN KEY(created_by) REFERENCES users(id),
  CONSTRAINT fk_unit_return_reviewer FOREIGN KEY(reviewed_by) REFERENCES users(id),
  CONSTRAINT fk_unit_return_signer FOREIGN KEY(signed_by) REFERENCES users(id),
  CONSTRAINT chk_unit_return_period CHECK(period_end>=period_start)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE return_final_snapshots (
  id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  return_id BIGINT UNSIGNED NOT NULL,
  snapshot_no INT UNSIGNED NOT NULL,
  payload_json JSON NOT NULL,
  source_fingerprint CHAR(64) NOT NULL,
  template_version_id BIGINT UNSIGNED NOT NULL,
  signed_by BIGINT UNSIGNED NOT NULL,
  signed_at DATETIME NOT NULL,
  locked_at DATETIME NOT NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  UNIQUE KEY uq_return_snapshot(return_id,snapshot_no),
  CONSTRAINT fk_return_snapshot_return FOREIGN KEY(return_id) REFERENCES unit_returns(id),
  CONSTRAINT fk_return_snapshot_template FOREIGN KEY(template_version_id) REFERENCES return_template_versions(id),
  CONSTRAINT fk_return_snapshot_signer FOREIGN KEY(signed_by) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE return_submissions (
  id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  snapshot_id BIGINT UNSIGNED NOT NULL,
  status ENUM('not_submitted','submitted','acknowledged','rejected','withdrawn') NOT NULL DEFAULT 'not_submitted',
  submitted_at DATETIME NULL,
  acknowledged_at DATETIME NULL,
  acknowledgement_reference VARCHAR(190) NULL,
  note VARCHAR(1000) NULL,
  updated_by BIGINT UNSIGNED NOT NULL,
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  UNIQUE KEY uq_return_submission(snapshot_id),
  CONSTRAINT fk_return_submission_snapshot FOREIGN KEY(snapshot_id) REFERENCES return_final_snapshots(id),
  CONSTRAINT fk_return_submission_actor FOREIGN KEY(updated_by) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE return_export_audit (
  id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  snapshot_id BIGINT UNSIGNED NOT NULL,
  format ENUM('pdf','csv','xlsx') NOT NULL,
  exported_by BIGINT UNSIGNED NOT NULL,
  exported_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  KEY idx_return_export_snapshot(snapshot_id,exported_at),
  CONSTRAINT fk_return_export_snapshot FOREIGN KEY(snapshot_id) REFERENCES return_final_snapshots(id),
  CONSTRAINT fk_return_export_actor FOREIGN KEY(exported_by) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TRIGGER return_final_snapshot_no_update BEFORE UPDATE ON return_final_snapshots FOR EACH ROW
 SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Final return snapshots are immutable';
CREATE TRIGGER return_final_snapshot_no_delete BEFORE DELETE ON return_final_snapshots FOR EACH ROW
 SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Final return snapshots are immutable';
CREATE TRIGGER return_export_audit_no_update BEFORE UPDATE ON return_export_audit FOR EACH ROW
 SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Return export audit is append-only';
CREATE TRIGGER return_export_audit_no_delete BEFORE DELETE ON return_export_audit FOR EACH ROW
 SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Return export audit is append-only';

INSERT INTO migration_history(version,name,checksum,execution_ms)
VALUES('202609280034','Versioned annual and installation returns',SHA2('202609280034_statutory_returns_v1',256),0);

-- Per-diner menu choices and catering handoffs.

ALTER TABLE event_programmes
 ADD COLUMN dining_mode ENUM('fixed','choice') NOT NULL DEFAULT 'fixed' AFTER capacity,
 ADD COLUMN menu_choice_enabled TINYINT(1) NOT NULL DEFAULT 0 AFTER dining_mode,
 ADD COLUMN menu_choice_cutoff DATETIME NULL AFTER menu_choice_enabled,
 ADD COLUMN published_menu_version_id BIGINT UNSIGNED NULL AFTER menu_choice_cutoff;

CREATE TABLE programme_menu_versions (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, programme_id BIGINT UNSIGNED NOT NULL,
 version_no INT UNSIGNED NOT NULL, status ENUM('draft','published','retired') NOT NULL DEFAULT 'draft',
 title VARCHAR(180) NOT NULL DEFAULT 'Dining menu', change_note VARCHAR(1000) NULL,
 published_by BIGINT UNSIGNED NULL, published_at DATETIME NULL, created_by BIGINT UNSIGNED NOT NULL,
 created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 UNIQUE KEY uq_programme_menu_version(programme_id,version_no), KEY idx_programme_menu_status(programme_id,status),
 FOREIGN KEY(programme_id) REFERENCES event_programmes(id) ON DELETE CASCADE,
 FOREIGN KEY(published_by) REFERENCES users(id) ON DELETE SET NULL, FOREIGN KEY(created_by) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

ALTER TABLE event_programmes ADD CONSTRAINT fk_programme_published_menu FOREIGN KEY(published_menu_version_id) REFERENCES programme_menu_versions(id) ON DELETE SET NULL;

CREATE TABLE programme_menu_courses (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, menu_version_id BIGINT UNSIGNED NOT NULL,
 stable_key CHAR(36) NOT NULL, name VARCHAR(120) NOT NULL, description VARCHAR(1000) NULL,
 display_order INT UNSIGNED NOT NULL, selection_required TINYINT(1) NOT NULL DEFAULT 0,
 UNIQUE KEY uq_menu_course_key(menu_version_id,stable_key), KEY idx_menu_course_order(menu_version_id,display_order),
 FOREIGN KEY(menu_version_id) REFERENCES programme_menu_versions(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE programme_menu_dishes (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, course_id BIGINT UNSIGNED NOT NULL,
 stable_key CHAR(36) NOT NULL, name VARCHAR(180) NOT NULL, description VARCHAR(1500) NULL,
 available TINYINT(1) NOT NULL DEFAULT 1, supplement DECIMAL(10,2) NOT NULL DEFAULT 0.00,
 allergen_summary VARCHAR(2000) NULL, allergen_verified_at DATETIME NULL, allergen_verified_by BIGINT UNSIGNED NULL,
 display_order INT UNSIGNED NOT NULL,
 UNIQUE KEY uq_menu_dish_key(course_id,stable_key), KEY idx_menu_dish_available(course_id,available,display_order),
 FOREIGN KEY(course_id) REFERENCES programme_menu_courses(id) ON DELETE CASCADE,
 FOREIGN KEY(allergen_verified_by) REFERENCES users(id) ON DELETE SET NULL,
 CONSTRAINT chk_dish_supplement CHECK(supplement>=0)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE programme_diners (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, programme_id BIGINT UNSIGNED NOT NULL,
 person_key CHAR(64) NOT NULL, booking_id BIGINT UNSIGNED NOT NULL, guest_id BIGINT UNSIGNED NULL,
 diner_kind ENUM('main','guest') NOT NULL, display_name VARCHAR(240) NOT NULL,
 dietary_requirements TEXT NULL, allergy_details TEXT NULL, active TINYINT(1) NOT NULL DEFAULT 1,
 created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 UNIQUE KEY uq_programme_diner(programme_id,person_key), UNIQUE KEY uq_booking_guest_diner(booking_id,guest_id),
 KEY idx_programme_diner_booking(booking_id,active), FOREIGN KEY(programme_id) REFERENCES event_programmes(id) ON DELETE CASCADE,
 FOREIGN KEY(booking_id) REFERENCES bookings(id) ON DELETE CASCADE, FOREIGN KEY(guest_id) REFERENCES guests(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE diner_menu_orders (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, diner_id BIGINT UNSIGNED NOT NULL,
 menu_version_id BIGINT UNSIGNED NOT NULL, status ENUM('current','needs_reselection','cancelled','superseded') NOT NULL DEFAULT 'current',
 base_charge DECIMAL(10,2) NOT NULL DEFAULT 0.00, supplement_total DECIMAL(10,2) NOT NULL DEFAULT 0.00,
 submitted_by_type ENUM('attendee','officer','system') NOT NULL, submitted_by_user_id BIGINT UNSIGNED NULL,
 late_reason VARCHAR(1000) NULL, submitted_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 UNIQUE KEY uq_diner_menu_order(diner_id,menu_version_id), KEY idx_menu_order_status(menu_version_id,status),
 FOREIGN KEY(diner_id) REFERENCES programme_diners(id) ON DELETE CASCADE, FOREIGN KEY(menu_version_id) REFERENCES programme_menu_versions(id),
 FOREIGN KEY(submitted_by_user_id) REFERENCES users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE diner_course_choices (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, order_id BIGINT UNSIGNED NOT NULL,
 course_id BIGINT UNSIGNED NOT NULL, dish_id BIGINT UNSIGNED NOT NULL, supplement DECIMAL(10,2) NOT NULL DEFAULT 0.00,
 UNIQUE KEY uq_diner_course_choice(order_id,course_id), KEY idx_dish_totals(dish_id,order_id),
 FOREIGN KEY(order_id) REFERENCES diner_menu_orders(id) ON DELETE CASCADE,
 FOREIGN KEY(course_id) REFERENCES programme_menu_courses(id), FOREIGN KEY(dish_id) REFERENCES programme_menu_dishes(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE diner_order_history (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, diner_id BIGINT UNSIGNED NOT NULL, order_id BIGINT UNSIGNED NULL,
 action VARCHAR(60) NOT NULL, detail_json JSON NOT NULL, actor_type ENUM('attendee','officer','system') NOT NULL,
 actor_user_id BIGINT UNSIGNED NULL, occurred_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 KEY idx_diner_history(diner_id,occurred_at,id), FOREIGN KEY(diner_id) REFERENCES programme_diners(id) ON DELETE CASCADE,
 FOREIGN KEY(order_id) REFERENCES diner_menu_orders(id) ON DELETE SET NULL, FOREIGN KEY(actor_user_id) REFERENCES users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE catering_handoffs (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, programme_id BIGINT UNSIGNED NOT NULL,
 handoff_no INT UNSIGNED NOT NULL, parent_handoff_id BIGINT UNSIGNED NULL, menu_version_id BIGINT UNSIGNED NOT NULL,
 snapshot_json LONGTEXT NOT NULL, comparison_json LONGTEXT NULL, approved_by BIGINT UNSIGNED NOT NULL,
 approved_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 UNIQUE KEY uq_catering_handoff(programme_id,handoff_no), FOREIGN KEY(programme_id) REFERENCES event_programmes(id),
 FOREIGN KEY(parent_handoff_id) REFERENCES catering_handoffs(id), FOREIGN KEY(menu_version_id) REFERENCES programme_menu_versions(id),
 FOREIGN KEY(approved_by) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE catering_handoff_delivery (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, handoff_id BIGINT UNSIGNED NOT NULL,
 status ENUM('delivered','acknowledged') NOT NULL, method ENUM('manual','email','other') NOT NULL DEFAULT 'manual',
 acknowledgement_reference VARCHAR(190) NULL, recorded_by BIGINT UNSIGNED NOT NULL, recorded_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 KEY idx_handoff_delivery(handoff_id,recorded_at,id), FOREIGN KEY(handoff_id) REFERENCES catering_handoffs(id) ON DELETE CASCADE,
 FOREIGN KEY(recorded_by) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TRIGGER diner_order_history_no_update BEFORE UPDATE ON diner_order_history FOR EACH ROW SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Diner order history is append-only';
CREATE TRIGGER diner_order_history_no_delete BEFORE DELETE ON diner_order_history FOR EACH ROW SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Diner order history is append-only';
CREATE TRIGGER catering_handoff_no_update BEFORE UPDATE ON catering_handoffs FOR EACH ROW SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Approved catering snapshots are immutable';
CREATE TRIGGER catering_handoff_no_delete BEFORE DELETE ON catering_handoffs FOR EACH ROW SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Catering handoffs are append-only';

INSERT INTO migration_history(version,name,checksum,execution_ms) VALUES('202609280035','Per-diner menu choices and catering handoffs',SHA2('202609280035_diner_menu_choices_v1',256),0);


CREATE TABLE dining_room_templates (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, unit_id BIGINT UNSIGNED NOT NULL,
 name VARCHAR(160) NOT NULL, room_width DECIMAL(8,2) NOT NULL DEFAULT 100, room_height DECIMAL(8,2) NOT NULL DEFAULT 70,
 active TINYINT(1) NOT NULL DEFAULT 1, created_by BIGINT UNSIGNED NOT NULL,
 created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 UNIQUE KEY uq_room_template(unit_id,name), FOREIGN KEY(unit_id) REFERENCES units(id) ON DELETE CASCADE,
 FOREIGN KEY(created_by) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE dining_room_template_tables (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, template_id BIGINT UNSIGNED NOT NULL,
 stable_key CHAR(36) NOT NULL, table_number VARCHAR(30) NOT NULL, table_name VARCHAR(100) NULL,
 shape ENUM('round','rectangular') NOT NULL DEFAULT 'round', is_top_table TINYINT(1) NOT NULL DEFAULT 0,
 x_pos DECIMAL(8,2) NOT NULL, y_pos DECIMAL(8,2) NOT NULL, width DECIMAL(8,2) NOT NULL, height DECIMAL(8,2) NOT NULL,
 capacity SMALLINT UNSIGNED NOT NULL, display_order INT UNSIGNED NOT NULL DEFAULT 0,
 UNIQUE KEY uq_template_table_key(template_id,stable_key), UNIQUE KEY uq_template_table_number(template_id,table_number),
 FOREIGN KEY(template_id) REFERENCES dining_room_templates(id) ON DELETE CASCADE,
 CONSTRAINT chk_template_table_size CHECK(width>0 AND height>0 AND capacity>0)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE programme_table_plans (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, programme_id BIGINT UNSIGNED NOT NULL,
 room_template_id BIGINT UNSIGNED NULL, title VARCHAR(180) NOT NULL, revision_no INT UNSIGNED NOT NULL DEFAULT 1,
 status ENUM('draft','review','published') NOT NULL DEFAULT 'draft', created_by BIGINT UNSIGNED NOT NULL,
 published_snapshot_id BIGINT UNSIGNED NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 UNIQUE KEY uq_programme_plan(programme_id), FOREIGN KEY(programme_id) REFERENCES event_programmes(id) ON DELETE CASCADE,
 FOREIGN KEY(room_template_id) REFERENCES dining_room_templates(id) ON DELETE SET NULL, FOREIGN KEY(created_by) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE programme_plan_tables (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, plan_id BIGINT UNSIGNED NOT NULL,
 stable_key CHAR(36) NOT NULL, table_number VARCHAR(30) NOT NULL, table_name VARCHAR(100) NULL,
 shape ENUM('round','rectangular') NOT NULL DEFAULT 'round', is_top_table TINYINT(1) NOT NULL DEFAULT 0,
 x_pos DECIMAL(8,2) NOT NULL, y_pos DECIMAL(8,2) NOT NULL, width DECIMAL(8,2) NOT NULL, height DECIMAL(8,2) NOT NULL,
 capacity SMALLINT UNSIGNED NOT NULL, display_order INT UNSIGNED NOT NULL DEFAULT 0,
 UNIQUE KEY uq_plan_table_key(plan_id,stable_key), UNIQUE KEY uq_plan_table_number(plan_id,table_number),
 FOREIGN KEY(plan_id) REFERENCES programme_table_plans(id) ON DELETE CASCADE,
 CONSTRAINT chk_plan_table_size CHECK(width>0 AND height>0 AND capacity>0)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE programme_plan_seats (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, table_id BIGINT UNSIGNED NOT NULL,
 seat_number SMALLINT UNSIGNED NOT NULL, x_pos DECIMAL(8,2) NULL, y_pos DECIMAL(8,2) NULL, locked TINYINT(1) NOT NULL DEFAULT 0,
 UNIQUE KEY uq_plan_seat(table_id,seat_number), FOREIGN KEY(table_id) REFERENCES programme_plan_tables(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE programme_seat_assignments (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, plan_id BIGINT UNSIGNED NOT NULL,
 seat_id BIGINT UNSIGNED NOT NULL, diner_id BIGINT UNSIGNED NOT NULL, assigned_by BIGINT UNSIGNED NOT NULL,
 assigned_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 UNIQUE KEY uq_plan_assigned_seat(seat_id), UNIQUE KEY uq_plan_assigned_diner(plan_id,diner_id),
 FOREIGN KEY(plan_id) REFERENCES programme_table_plans(id) ON DELETE CASCADE,
 FOREIGN KEY(seat_id) REFERENCES programme_plan_seats(id) ON DELETE CASCADE,
 FOREIGN KEY(diner_id) REFERENCES programme_diners(id) ON DELETE CASCADE, FOREIGN KEY(assigned_by) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE programme_seating_preferences (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, programme_id BIGINT UNSIGNED NOT NULL,
 diner_id BIGINT UNSIGNED NOT NULL, preference_type ENUM('sit_together','keep_apart','honoured_visitor','accessibility','host_pairing') NOT NULL,
 related_diner_id BIGINT UNSIGNED NULL, hard_constraint TINYINT(1) NOT NULL DEFAULT 0,
 public_label VARCHAR(160) NULL, private_reason_cipher TEXT NULL, active TINYINT(1) NOT NULL DEFAULT 1,
 created_by BIGINT UNSIGNED NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 KEY idx_seating_preference(programme_id,diner_id,active), FOREIGN KEY(programme_id) REFERENCES event_programmes(id) ON DELETE CASCADE,
 FOREIGN KEY(diner_id) REFERENCES programme_diners(id) ON DELETE CASCADE, FOREIGN KEY(related_diner_id) REFERENCES programme_diners(id) ON DELETE CASCADE,
 FOREIGN KEY(created_by) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE programme_table_plan_revisions (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, plan_id BIGINT UNSIGNED NOT NULL,
 revision_no INT UNSIGNED NOT NULL, action VARCHAR(60) NOT NULL, change_json JSON NOT NULL,
 actor_user_id BIGINT UNSIGNED NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 UNIQUE KEY uq_plan_revision(plan_id,revision_no), FOREIGN KEY(plan_id) REFERENCES programme_table_plans(id) ON DELETE CASCADE,
 FOREIGN KEY(actor_user_id) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE programme_table_plan_snapshots (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, plan_id BIGINT UNSIGNED NOT NULL,
 snapshot_no INT UNSIGNED NOT NULL, payload_json LONGTEXT NOT NULL, source_fingerprint CHAR(64) NOT NULL,
 published_by BIGINT UNSIGNED NOT NULL, published_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 UNIQUE KEY uq_plan_snapshot(plan_id,snapshot_no), FOREIGN KEY(plan_id) REFERENCES programme_table_plans(id),
 FOREIGN KEY(published_by) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

ALTER TABLE programme_table_plans ADD CONSTRAINT fk_plan_published_snapshot FOREIGN KEY(published_snapshot_id) REFERENCES programme_table_plan_snapshots(id) ON DELETE SET NULL;
CREATE TRIGGER plan_revision_no_update BEFORE UPDATE ON programme_table_plan_revisions FOR EACH ROW SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Table plan history is append-only';
CREATE TRIGGER plan_revision_no_delete BEFORE DELETE ON programme_table_plan_revisions FOR EACH ROW SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Table plan history is append-only';
CREATE TRIGGER plan_snapshot_no_update BEFORE UPDATE ON programme_table_plan_snapshots FOR EACH ROW SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Published table plans are immutable';
CREATE TRIGGER plan_snapshot_no_delete BEFORE DELETE ON programme_table_plan_snapshots FOR EACH ROW SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Published table plans are immutable';

INSERT INTO migration_history(version,name,checksum,execution_ms) VALUES('202609280036','Shared-programme dining table plans',SHA2('202609280036_programme_table_plans_v1',256),0);

CREATE TABLE event_day_packs (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
 unit_id BIGINT UNSIGNED NOT NULL, meeting_id BIGINT UNSIGNED NOT NULL, programme_id BIGINT UNSIGNED NOT NULL,
 seating_snapshot_id BIGINT UNSIGNED NOT NULL, generated_by BIGINT UNSIGNED NOT NULL,
 generated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, expires_at DATETIME NOT NULL, purged_at DATETIME NULL,
 source_fingerprint CHAR(64) NOT NULL, snapshot_json LONGTEXT NULL, restricted_snapshot_cipher LONGTEXT NULL,
 KEY idx_event_pack_event(meeting_id,generated_at,id), KEY idx_event_pack_expiry(expires_at,purged_at),
 FOREIGN KEY(unit_id) REFERENCES units(id), FOREIGN KEY(meeting_id) REFERENCES meetings(id),
 FOREIGN KEY(programme_id) REFERENCES event_programmes(id), FOREIGN KEY(seating_snapshot_id) REFERENCES programme_table_plan_snapshots(id),
 FOREIGN KEY(generated_by) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE event_day_pack_people (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, pack_id BIGINT UNSIGNED NOT NULL,
 person_type ENUM('booking','guest') NOT NULL, person_id BIGINT UNSIGNED NOT NULL,
 display_name VARCHAR(240) NOT NULL, booking_reference VARCHAR(50) NOT NULL,
 UNIQUE KEY uq_pack_person(pack_id,person_type,person_id), KEY idx_pack_people(pack_id,display_name,id),
 FOREIGN KEY(pack_id) REFERENCES event_day_packs(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE offline_checkin_batches (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, pack_id BIGINT UNSIGNED NOT NULL,
 unit_id BIGINT UNSIGNED NOT NULL, meeting_id BIGINT UNSIGNED NOT NULL,
 status ENUM('review','approved','applied','cancelled') NOT NULL DEFAULT 'review',
 created_by BIGINT UNSIGNED NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 reviewed_by BIGINT UNSIGNED NULL, reviewed_at DATETIME NULL, applied_by BIGINT UNSIGNED NULL, applied_at DATETIME NULL,
 KEY idx_offline_batch_event(meeting_id,status,created_at), FOREIGN KEY(pack_id) REFERENCES event_day_packs(id),
 FOREIGN KEY(unit_id) REFERENCES units(id), FOREIGN KEY(meeting_id) REFERENCES meetings(id),
 FOREIGN KEY(created_by) REFERENCES users(id), FOREIGN KEY(reviewed_by) REFERENCES users(id), FOREIGN KEY(applied_by) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE offline_checkin_rows (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, batch_id BIGINT UNSIGNED NOT NULL,
 person_type ENUM('booking','guest') NOT NULL, person_id BIGINT UNSIGNED NOT NULL,
 marked_arrived_at DATETIME NULL,
 review_state ENUM('ready','duplicate','conflict') NOT NULL,
 review_decision ENUM('pending','apply','skip') NOT NULL DEFAULT 'pending',
 result_state ENUM('pending','applied','duplicate','conflict','skipped') NOT NULL DEFAULT 'pending',
 live_arrived_at_review DATETIME NULL, detail_code VARCHAR(60) NULL,
 UNIQUE KEY uq_offline_batch_person(batch_id,person_type,person_id),
 FOREIGN KEY(batch_id) REFERENCES offline_checkin_batches(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

INSERT INTO migration_history(version,name,checksum,execution_ms)
VALUES('202609280037','Short-lived event-day packs and offline check-in reconciliation',SHA2('202609280037_event_day_packs_v1',256),0);

CREATE TABLE finance_ledger_entries (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
 province_id BIGINT UNSIGNED NOT NULL, unit_id BIGINT UNSIGNED NOT NULL,
 meeting_id BIGINT UNSIGNED NOT NULL, booking_id BIGINT UNSIGNED NOT NULL,
 entry_type ENUM('charge','adjustment','exemption','payment','refund','reversal') NOT NULL,
 amount DECIMAL(12,2) NOT NULL, balance_delta DECIMAL(12,2) NOT NULL,
 original_entry_id BIGINT UNSIGNED NULL, effective_at DATETIME NOT NULL,
 method VARCHAR(50) NULL, safe_reference VARCHAR(100) NULL, finance_note_cipher LONGTEXT NULL,
 source ENUM('legacy','officer','reconciliation') NOT NULL DEFAULT 'officer', created_by BIGINT UNSIGNED NULL,
 created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 UNIQUE KEY uq_finance_reversal(original_entry_id), KEY idx_finance_booking(booking_id,id),
 KEY idx_finance_event(meeting_id,effective_at,id), KEY idx_finance_unit(unit_id,effective_at,id),
 FOREIGN KEY(province_id) REFERENCES provinces(id), FOREIGN KEY(unit_id) REFERENCES units(id),
 FOREIGN KEY(meeting_id) REFERENCES meetings(id), FOREIGN KEY(booking_id) REFERENCES bookings(id),
 FOREIGN KEY(original_entry_id) REFERENCES finance_ledger_entries(id), FOREIGN KEY(created_by) REFERENCES users(id) ON DELETE SET NULL,
 CHECK(amount>0), CHECK((entry_type IN('charge','refund') AND balance_delta>0) OR (entry_type IN('exemption','payment') AND balance_delta<0) OR entry_type IN('adjustment','reversal'))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE finance_allocations (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, payment_entry_id BIGINT UNSIGNED NOT NULL,
 charge_entry_id BIGINT UNSIGNED NOT NULL, amount DECIMAL(12,2) NOT NULL,
 created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 UNIQUE KEY uq_finance_allocation(payment_entry_id,charge_entry_id), KEY idx_finance_charge_allocation(charge_entry_id),
 FOREIGN KEY(payment_entry_id) REFERENCES finance_ledger_entries(id), FOREIGN KEY(charge_entry_id) REFERENCES finance_ledger_entries(id),
 CHECK(amount>0)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE finance_bank_accounts (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, scope_key VARCHAR(80) NOT NULL,
 scope_type ENUM('unit','province') NOT NULL, province_id BIGINT UNSIGNED NOT NULL, unit_id BIGINT UNSIGNED NULL,
 issuer_name VARCHAR(180) NOT NULL, account_name_cipher LONGTEXT NOT NULL,
 sort_code_cipher LONGTEXT NOT NULL, account_number_cipher LONGTEXT NOT NULL,
 sort_code_mask VARCHAR(30) NOT NULL, account_number_mask VARCHAR(50) NOT NULL,
 updated_by BIGINT UNSIGNED NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 UNIQUE KEY uq_finance_bank_scope(scope_key), FOREIGN KEY(province_id) REFERENCES provinces(id),
 FOREIGN KEY(unit_id) REFERENCES units(id), FOREIGN KEY(updated_by) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE finance_bank_account_history (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, bank_account_id BIGINT UNSIGNED NOT NULL,
 scope_key VARCHAR(80) NOT NULL, old_sort_mask VARCHAR(30) NULL, old_account_mask VARCHAR(50) NULL,
 new_sort_mask VARCHAR(30) NOT NULL, new_account_mask VARCHAR(50) NOT NULL,
 changed_by BIGINT UNSIGNED NOT NULL, changed_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 FOREIGN KEY(bank_account_id) REFERENCES finance_bank_accounts(id), FOREIGN KEY(changed_by) REFERENCES users(id),
 KEY idx_bank_history(bank_account_id,changed_at,id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

INSERT INTO finance_ledger_entries(province_id,unit_id,meeting_id,booking_id,entry_type,amount,balance_delta,effective_at,source)
SELECT u.province_id,m.unit_id,b.meeting_id,b.id,'charge',b.total_charge,b.total_charge,b.created_at,'legacy'
FROM bookings b JOIN meetings m ON m.id=b.meeting_id JOIN units u ON u.id=m.unit_id WHERE b.total_charge>0;
INSERT INTO finance_ledger_entries(province_id,unit_id,meeting_id,booking_id,entry_type,amount,balance_delta,effective_at,method,safe_reference,source)
SELECT u.province_id,m.unit_id,b.meeting_id,b.id,'payment',b.amount_received,-b.amount_received,COALESCE(b.paid_at,b.updated_at),b.payment_method,b.payment_reference,'legacy'
FROM bookings b JOIN meetings m ON m.id=b.meeting_id JOIN units u ON u.id=m.unit_id WHERE b.amount_received>0;
INSERT INTO finance_ledger_entries(province_id,unit_id,meeting_id,booking_id,entry_type,amount,balance_delta,effective_at,source)
SELECT u.province_id,m.unit_id,b.meeting_id,b.id,'refund',b.refund_amount,b.refund_amount,COALESCE(b.refunded_at,b.updated_at),'legacy'
FROM bookings b JOIN meetings m ON m.id=b.meeting_id JOIN units u ON u.id=m.unit_id WHERE b.refund_amount>0;
INSERT INTO finance_ledger_entries(province_id,unit_id,meeting_id,booking_id,entry_type,amount,balance_delta,effective_at,source)
SELECT u.province_id,m.unit_id,b.meeting_id,b.id,'exemption',b.total_charge,-b.total_charge,b.updated_at,'legacy'
FROM bookings b JOIN meetings m ON m.id=b.meeting_id JOIN units u ON u.id=m.unit_id WHERE b.payment_status='exempt' AND b.total_charge>0;

CREATE TRIGGER finance_entry_no_update BEFORE UPDATE ON finance_ledger_entries FOR EACH ROW SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Finance ledger entries are immutable';
CREATE TRIGGER finance_entry_no_delete BEFORE DELETE ON finance_ledger_entries FOR EACH ROW SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Finance ledger entries are immutable';
CREATE TRIGGER finance_allocation_no_update BEFORE UPDATE ON finance_allocations FOR EACH ROW SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Finance allocations are immutable';
CREATE TRIGGER finance_allocation_no_delete BEFORE DELETE ON finance_allocations FOR EACH ROW SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Finance allocations are immutable';

INSERT INTO migration_history(version,name,checksum,execution_ms)
VALUES('202609280038','Immutable finance ledger and protected bank accounts',SHA2('202609280038_finance_ledger_v1',256),0);



ALTER TABLE finance_ledger_entries
  MODIFY meeting_id BIGINT UNSIGNED NULL,
  MODIFY booking_id BIGINT UNSIGNED NULL,
  ADD person_id BIGINT UNSIGNED NULL AFTER booking_id,
  ADD member_id BIGINT UNSIGNED NULL AFTER person_id,
  ADD source_type VARCHAR(40) NULL AFTER source,
  ADD source_id BIGINT UNSIGNED NULL AFTER source_type,
  ADD KEY idx_finance_person(unit_id,person_id,effective_at,id),
  ADD KEY idx_finance_source(unit_id,source_type,source_id),
  ADD CONSTRAINT fk_finance_person FOREIGN KEY(person_id) REFERENCES people(id),
  ADD CONSTRAINT fk_finance_member FOREIGN KEY(member_id) REFERENCES unit_members(id);

DROP TRIGGER finance_entry_no_update;
UPDATE finance_ledger_entries SET source_type='booking',source_id=booking_id WHERE source_type IS NULL AND booking_id IS NOT NULL;
CREATE TRIGGER finance_entry_no_update BEFORE UPDATE ON finance_ledger_entries FOR EACH ROW SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Finance ledger entries are immutable';

CREATE TABLE finance_funds (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, province_id BIGINT UNSIGNED NOT NULL, unit_id BIGINT UNSIGNED NOT NULL,
 name VARCHAR(160) NOT NULL, fund_type ENUM('operating','charity_designated') NOT NULL DEFAULT 'operating',
 purpose VARCHAR(500) NULL, active TINYINT(1) NOT NULL DEFAULT 1, created_by BIGINT UNSIGNED NOT NULL,
 created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 UNIQUE KEY uq_finance_fund(unit_id,name), KEY idx_finance_fund_scope(unit_id,fund_type,active),
 FOREIGN KEY(province_id) REFERENCES provinces(id), FOREIGN KEY(unit_id) REFERENCES units(id), FOREIGN KEY(created_by) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE finance_cash_entries (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, province_id BIGINT UNSIGNED NOT NULL, unit_id BIGINT UNSIGNED NOT NULL,
 ledger_entry_id BIGINT UNSIGNED NULL, fund_id BIGINT UNSIGNED NULL, direction ENUM('in','out') NOT NULL,
 category ENUM('event_payment','event_refund','subscription_payment','opening_balance','donation','purchase_bill','expense_claim','charity_disbursement','other') NOT NULL,
 amount DECIMAL(12,2) NOT NULL, effective_at DATETIME NOT NULL, source_type VARCHAR(40) NULL, source_id BIGINT UNSIGNED NULL,
 created_by BIGINT UNSIGNED NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 UNIQUE KEY uq_cash_ledger(ledger_entry_id), KEY idx_cash_unit(unit_id,effective_at,id), KEY idx_cash_fund(fund_id,effective_at,id),
 FOREIGN KEY(province_id) REFERENCES provinces(id), FOREIGN KEY(unit_id) REFERENCES units(id), FOREIGN KEY(ledger_entry_id) REFERENCES finance_ledger_entries(id),
 FOREIGN KEY(fund_id) REFERENCES finance_funds(id), FOREIGN KEY(created_by) REFERENCES users(id) ON DELETE SET NULL, CHECK(amount>0)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

INSERT INTO finance_cash_entries(province_id,unit_id,ledger_entry_id,direction,category,amount,effective_at,source_type,source_id,created_by)
SELECT province_id,unit_id,id,'in','event_payment',amount,effective_at,'booking',booking_id,created_by FROM finance_ledger_entries WHERE entry_type='payment';
INSERT INTO finance_cash_entries(province_id,unit_id,ledger_entry_id,direction,category,amount,effective_at,source_type,source_id,created_by)
SELECT province_id,unit_id,id,'out','event_refund',amount,effective_at,'booking',booking_id,created_by FROM finance_ledger_entries WHERE entry_type='refund';

CREATE TABLE subscription_reminder_policies (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, unit_id BIGINT UNSIGNED NOT NULL, name VARCHAR(120) NOT NULL,
 enabled TINYINT(1) NOT NULL DEFAULT 0, first_after_days SMALLINT UNSIGNED NOT NULL DEFAULT 14, repeat_days SMALLINT UNSIGNED NOT NULL DEFAULT 30,
 maximum_reminders TINYINT UNSIGNED NOT NULL DEFAULT 3, subject VARCHAR(180) NOT NULL, message_text VARCHAR(2000) NOT NULL,
 authorised_by BIGINT UNSIGNED NULL, authorised_at DATETIME NULL, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 UNIQUE KEY uq_reminder_policy(unit_id,name), FOREIGN KEY(unit_id) REFERENCES units(id), FOREIGN KEY(authorised_by) REFERENCES users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE subscription_periods (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, province_id BIGINT UNSIGNED NOT NULL, unit_id BIGINT UNSIGNED NOT NULL,
 label VARCHAR(120) NOT NULL, starts_on DATE NOT NULL, ends_on DATE NOT NULL, due_on DATE NOT NULL,
 proration_method ENUM('none','daily','monthly') NOT NULL DEFAULT 'none', status ENUM('draft','approved','closed') NOT NULL DEFAULT 'draft',
 reminder_policy_id BIGINT UNSIGNED NULL, approved_by BIGINT UNSIGNED NULL, approved_at DATETIME NULL,
 created_by BIGINT UNSIGNED NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 UNIQUE KEY uq_subscription_period(unit_id,starts_on,ends_on), KEY idx_subscription_period(unit_id,status,due_on),
 FOREIGN KEY(province_id) REFERENCES provinces(id), FOREIGN KEY(unit_id) REFERENCES units(id), FOREIGN KEY(reminder_policy_id) REFERENCES subscription_reminder_policies(id),
 FOREIGN KEY(approved_by) REFERENCES users(id) ON DELETE SET NULL, FOREIGN KEY(created_by) REFERENCES users(id), CHECK(starts_on<=ends_on)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE subscription_rates (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, period_id BIGINT UNSIGNED NOT NULL, membership_type VARCHAR(80) NOT NULL,
 amount DECIMAL(12,2) NOT NULL, allow_proration TINYINT(1) NOT NULL DEFAULT 0, approved_by BIGINT UNSIGNED NULL, approved_at DATETIME NULL,
 created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uq_subscription_rate(period_id,membership_type),
 FOREIGN KEY(period_id) REFERENCES subscription_periods(id), FOREIGN KEY(approved_by) REFERENCES users(id) ON DELETE SET NULL, CHECK(amount>=0)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE finance_invoices (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, unit_id BIGINT UNSIGNED NOT NULL, invoice_number VARCHAR(60) NOT NULL,
 person_id BIGINT UNSIGNED NOT NULL, issued_on DATE NOT NULL, due_on DATE NOT NULL, total DECIMAL(12,2) NOT NULL,
 status ENUM('issued','settled','cancelled') NOT NULL DEFAULT 'issued', created_by BIGINT UNSIGNED NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 UNIQUE KEY uq_invoice_number(unit_id,invoice_number), FOREIGN KEY(unit_id) REFERENCES units(id), FOREIGN KEY(person_id) REFERENCES people(id), FOREIGN KEY(created_by) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE subscription_charges (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, province_id BIGINT UNSIGNED NOT NULL, unit_id BIGINT UNSIGNED NOT NULL,
 period_id BIGINT UNSIGNED NOT NULL, rate_id BIGINT UNSIGNED NOT NULL, member_id BIGINT UNSIGNED NOT NULL, person_id BIGINT UNSIGNED NOT NULL,
 membership_type_snapshot VARCHAR(80) NOT NULL, membership_start_snapshot DATE NOT NULL, base_amount DECIMAL(12,2) NOT NULL,
 charged_amount DECIMAL(12,2) NOT NULL, proration_note VARCHAR(250) NULL, status ENUM('charged','exempt','reversed') NOT NULL DEFAULT 'charged',
 invoice_id BIGINT UNSIGNED NULL, ledger_entry_id BIGINT UNSIGNED NOT NULL, created_by BIGINT UNSIGNED NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 UNIQUE KEY uq_subscription_charge(period_id,person_id), KEY idx_subscription_person(unit_id,person_id,period_id),
 FOREIGN KEY(province_id) REFERENCES provinces(id), FOREIGN KEY(unit_id) REFERENCES units(id), FOREIGN KEY(period_id) REFERENCES subscription_periods(id),
 FOREIGN KEY(rate_id) REFERENCES subscription_rates(id), FOREIGN KEY(member_id) REFERENCES unit_members(id), FOREIGN KEY(person_id) REFERENCES people(id),
 FOREIGN KEY(invoice_id) REFERENCES finance_invoices(id), FOREIGN KEY(ledger_entry_id) REFERENCES finance_ledger_entries(id), FOREIGN KEY(created_by) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE subscription_exemptions (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, charge_id BIGINT UNSIGNED NOT NULL, unit_id BIGINT UNSIGNED NOT NULL,
 amount DECIMAL(12,2) NOT NULL, reason VARCHAR(500) NOT NULL, ledger_entry_id BIGINT UNSIGNED NOT NULL,
 approved_by BIGINT UNSIGNED NOT NULL, approved_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 KEY idx_subscription_exemption(charge_id,id), FOREIGN KEY(charge_id) REFERENCES subscription_charges(id),
 FOREIGN KEY(unit_id) REFERENCES units(id), FOREIGN KEY(ledger_entry_id) REFERENCES finance_ledger_entries(id),
 FOREIGN KEY(approved_by) REFERENCES users(id), CHECK(amount>0)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE subscription_reminder_log (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, charge_id BIGINT UNSIGNED NOT NULL, policy_id BIGINT UNSIGNED NOT NULL,
 sequence_number TINYINT UNSIGNED NOT NULL, queued_by BIGINT UNSIGNED NOT NULL, queued_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 UNIQUE KEY uq_subscription_reminder(charge_id,sequence_number), FOREIGN KEY(charge_id) REFERENCES subscription_charges(id),
 FOREIGN KEY(policy_id) REFERENCES subscription_reminder_policies(id), FOREIGN KEY(queued_by) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE finance_opening_balances (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, unit_id BIGINT UNSIGNED NOT NULL, person_id BIGINT UNSIGNED NOT NULL,
 as_of_date DATE NOT NULL, amount DECIMAL(12,2) NOT NULL, balance_kind ENUM('amount_due','credit') NOT NULL,
 ledger_entry_id BIGINT UNSIGNED NOT NULL, reason VARCHAR(500) NOT NULL, approved_by BIGINT UNSIGNED NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 UNIQUE KEY uq_opening_balance(unit_id,person_id,as_of_date), FOREIGN KEY(unit_id) REFERENCES units(id), FOREIGN KEY(person_id) REFERENCES people(id),
 FOREIGN KEY(ledger_entry_id) REFERENCES finance_ledger_entries(id), FOREIGN KEY(approved_by) REFERENCES users(id), CHECK(amount>0)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE finance_expense_items (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, province_id BIGINT UNSIGNED NOT NULL, unit_id BIGINT UNSIGNED NOT NULL,
 item_type ENUM('purchase_bill','expense_claim') NOT NULL, claimant_user_id BIGINT UNSIGNED NULL, claimant_name VARCHAR(180) NULL,
 supplier VARCHAR(180) NULL, description VARCHAR(500) NOT NULL, amount DECIMAL(12,2) NOT NULL, fund_id BIGINT UNSIGNED NULL,
 state ENUM('draft','submitted','approved','paid','rejected') NOT NULL DEFAULT 'draft', finance_note_cipher LONGTEXT NULL,
 submitted_by BIGINT UNSIGNED NULL, submitted_at DATETIME NULL, approved_by BIGINT UNSIGNED NULL, approved_at DATETIME NULL,
 rejected_by BIGINT UNSIGNED NULL, rejected_at DATETIME NULL, paid_by BIGINT UNSIGNED NULL, paid_at DATETIME NULL,
 ledger_entry_id BIGINT UNSIGNED NULL, created_by BIGINT UNSIGNED NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 KEY idx_expense_unit(unit_id,state,id), FOREIGN KEY(province_id) REFERENCES provinces(id), FOREIGN KEY(unit_id) REFERENCES units(id),
 FOREIGN KEY(claimant_user_id) REFERENCES users(id) ON DELETE SET NULL, FOREIGN KEY(fund_id) REFERENCES finance_funds(id),
 FOREIGN KEY(submitted_by) REFERENCES users(id) ON DELETE SET NULL, FOREIGN KEY(approved_by) REFERENCES users(id) ON DELETE SET NULL,
 FOREIGN KEY(rejected_by) REFERENCES users(id) ON DELETE SET NULL, FOREIGN KEY(paid_by) REFERENCES users(id) ON DELETE SET NULL,
 FOREIGN KEY(ledger_entry_id) REFERENCES finance_ledger_entries(id), FOREIGN KEY(created_by) REFERENCES users(id), CHECK(amount>0)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE finance_expense_receipts (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, expense_id BIGINT UNSIGNED NOT NULL, unit_id BIGINT UNSIGNED NOT NULL,
 storage_key CHAR(64) NOT NULL, original_filename VARCHAR(180) NOT NULL, mime_type VARCHAR(100) NOT NULL, byte_size BIGINT UNSIGNED NOT NULL,
 sha256 CHAR(64) NOT NULL, uploaded_by BIGINT UNSIGNED NOT NULL, uploaded_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 UNIQUE KEY uq_expense_storage(storage_key), KEY idx_expense_receipt(expense_id,id), FOREIGN KEY(expense_id) REFERENCES finance_expense_items(id),
 FOREIGN KEY(unit_id) REFERENCES units(id), FOREIGN KEY(uploaded_by) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE finance_year_end_snapshots (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, province_id BIGINT UNSIGNED NOT NULL, unit_id BIGINT UNSIGNED NOT NULL,
 period_start DATE NOT NULL, period_end DATE NOT NULL, snapshot_number INT UNSIGNED NOT NULL, status ENUM('draft','reviewed','locked') NOT NULL DEFAULT 'draft',
 payload_json LONGTEXT NOT NULL, source_hash CHAR(64) NOT NULL, created_by BIGINT UNSIGNED NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 reviewed_by BIGINT UNSIGNED NULL, reviewed_at DATETIME NULL, locked_by BIGINT UNSIGNED NULL, locked_at DATETIME NULL,
 UNIQUE KEY uq_year_snapshot(unit_id,period_start,period_end,snapshot_number), KEY idx_year_snapshot(unit_id,period_end,status),
 FOREIGN KEY(province_id) REFERENCES provinces(id), FOREIGN KEY(unit_id) REFERENCES units(id), FOREIGN KEY(created_by) REFERENCES users(id),
 FOREIGN KEY(reviewed_by) REFERENCES users(id) ON DELETE SET NULL, FOREIGN KEY(locked_by) REFERENCES users(id) ON DELETE SET NULL,
 CHECK(period_start<=period_end)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TRIGGER finance_cash_no_update BEFORE UPDATE ON finance_cash_entries FOR EACH ROW SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Cash entries are immutable';
CREATE TRIGGER finance_cash_no_delete BEFORE DELETE ON finance_cash_entries FOR EACH ROW SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Cash entries are immutable';

INSERT INTO migration_history(version,name,checksum,execution_ms)
VALUES('202609280039','Subscriptions expenses designated funds and year end snapshots',SHA2('202609280039_subscriptions_expenses_v1',256),0);

SET NAMES utf8mb4;
START TRANSACTION;

CREATE TABLE IF NOT EXISTS invoice_issuer_settings (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, issuer_scope_key VARCHAR(80) NOT NULL,
 scope_type ENUM('unit','province') NOT NULL, province_id BIGINT UNSIGNED NOT NULL, unit_id BIGINT UNSIGNED NULL,
 issuer_name VARCHAR(180) NOT NULL, issuer_address VARCHAR(1000) NOT NULL, issuer_email VARCHAR(254) NULL,
 registration_number VARCHAR(100) NULL, vat_treatment ENUM('not_registered','registered','outside_scope') NOT NULL DEFAULT 'not_registered',
 vat_number VARCHAR(80) NULL, default_vat_rate DECIMAL(5,2) NOT NULL DEFAULT 0,
 invoice_prefix VARCHAR(30) NOT NULL DEFAULT 'INV-', invoice_padding TINYINT UNSIGNED NOT NULL DEFAULT 5, next_invoice_number BIGINT UNSIGNED NOT NULL DEFAULT 1,
 credit_prefix VARCHAR(30) NOT NULL DEFAULT 'CN-', credit_padding TINYINT UNSIGNED NOT NULL DEFAULT 5, next_credit_number BIGINT UNSIGNED NOT NULL DEFAULT 1,
 payment_terms_days SMALLINT UNSIGNED NOT NULL DEFAULT 14, payment_terms_text VARCHAR(1000) NULL, payment_instructions VARCHAR(2000) NULL,
 updated_by BIGINT UNSIGNED NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 UNIQUE KEY uq_invoice_issuer_scope(issuer_scope_key), FOREIGN KEY(province_id) REFERENCES provinces(id),
 FOREIGN KEY(unit_id) REFERENCES units(id), FOREIGN KEY(updated_by) REFERENCES users(id),
 CHECK(invoice_padding BETWEEN 1 AND 12), CHECK(credit_padding BETWEEN 1 AND 12), CHECK(default_vat_rate>=0 AND default_vat_rate<=100)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- MariaDB cannot rebuild a table while another table retains a foreign key to
-- it. Remove and restore the existing optional subscription link explicitly.
ALTER TABLE subscription_charges DROP FOREIGN KEY subscription_charges_ibfk_7;
ALTER TABLE finance_invoices
 DROP INDEX uq_invoice_number,
 ADD province_id BIGINT UNSIGNED NULL AFTER id,
 ADD scope_type ENUM('unit','province') NOT NULL DEFAULT 'unit' AFTER province_id,
 ADD issuer_scope_key VARCHAR(80) NULL AFTER scope_type,
 MODIFY unit_id BIGINT UNSIGNED NULL,
 MODIFY invoice_number VARCHAR(60) NULL,
 MODIFY person_id BIGINT UNSIGNED NULL,
 ADD booking_id BIGINT UNSIGNED NULL AFTER person_id,
 ADD recipient_name VARCHAR(220) NOT NULL DEFAULT '' AFTER booking_id,
 ADD recipient_email VARCHAR(254) NULL AFTER recipient_name,
 MODIFY issued_on DATE NULL,
 MODIFY due_on DATE NULL,
 MODIFY total DECIMAL(12,2) NOT NULL DEFAULT 0,
 MODIFY status ENUM('draft','issued','partly_paid','paid','voided','credited') NOT NULL DEFAULT 'draft',
 ADD currency_code CHAR(3) NOT NULL DEFAULT 'GBP' AFTER total,
 ADD replaces_invoice_id BIGINT UNSIGNED NULL AFTER currency_code,
 ADD issued_by BIGINT UNSIGNED NULL AFTER created_by,
 ADD issued_at DATETIME NULL AFTER issued_by,
 ADD voided_by BIGINT UNSIGNED NULL AFTER issued_at,
 ADD voided_at DATETIME NULL AFTER voided_by,
 ADD updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 ADD UNIQUE KEY uq_invoice_scope_number(issuer_scope_key,invoice_number),
 ADD KEY idx_invoice_unit(unit_id), ADD KEY idx_invoice_scope(issuer_scope_key,status,id), ADD KEY idx_invoice_booking(booking_id),
 ADD CONSTRAINT fk_invoice_province FOREIGN KEY(province_id) REFERENCES provinces(id),
 ADD CONSTRAINT fk_invoice_booking FOREIGN KEY(booking_id) REFERENCES bookings(id),
 ADD CONSTRAINT fk_invoice_issuer FOREIGN KEY(issued_by) REFERENCES users(id) ON DELETE SET NULL,
 ADD CONSTRAINT fk_invoice_voider FOREIGN KEY(voided_by) REFERENCES users(id) ON DELETE SET NULL;
ALTER TABLE finance_invoices ADD CONSTRAINT fk_invoice_replacement FOREIGN KEY(replaces_invoice_id) REFERENCES finance_invoices(id);
ALTER TABLE subscription_charges ADD CONSTRAINT subscription_charges_ibfk_7 FOREIGN KEY(invoice_id) REFERENCES finance_invoices(id);

CREATE TABLE finance_invoice_lines (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, invoice_id BIGINT UNSIGNED NOT NULL, line_number SMALLINT UNSIGNED NOT NULL,
 description VARCHAR(500) NOT NULL, quantity DECIMAL(10,2) NOT NULL DEFAULT 1, unit_price DECIMAL(12,2) NOT NULL,
 vat_rate DECIMAL(5,2) NOT NULL DEFAULT 0, net_amount DECIMAL(12,2) NOT NULL, vat_amount DECIMAL(12,2) NOT NULL DEFAULT 0,
 line_total DECIMAL(12,2) NOT NULL, ledger_entry_id BIGINT UNSIGNED NULL,
 source_type ENUM('booking_charge','subscription_charge','manual_charge') NOT NULL, source_id BIGINT UNSIGNED NULL,
 approved_by BIGINT UNSIGNED NULL, approved_at DATETIME NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 UNIQUE KEY uq_invoice_line(invoice_id,line_number), KEY idx_invoice_line_ledger(ledger_entry_id),
 FOREIGN KEY(invoice_id) REFERENCES finance_invoices(id), FOREIGN KEY(ledger_entry_id) REFERENCES finance_ledger_entries(id),
 FOREIGN KEY(approved_by) REFERENCES users(id) ON DELETE SET NULL,
 CHECK(quantity>0), CHECK(vat_rate>=0 AND vat_rate<=100), CHECK(line_total=net_amount+vat_amount)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE finance_invoice_issue_snapshots (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, invoice_id BIGINT UNSIGNED NOT NULL, snapshot_cipher LONGTEXT NOT NULL,
 snapshot_hash CHAR(64) NOT NULL, issued_by BIGINT UNSIGNED NOT NULL, issued_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 UNIQUE KEY uq_invoice_issue_snapshot(invoice_id), FOREIGN KEY(invoice_id) REFERENCES finance_invoices(id), FOREIGN KEY(issued_by) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE finance_invoice_payment_allocations (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, invoice_id BIGINT UNSIGNED NOT NULL, payment_entry_id BIGINT UNSIGNED NOT NULL,
 amount DECIMAL(12,2) NOT NULL, allocated_by BIGINT UNSIGNED NOT NULL, allocated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 UNIQUE KEY uq_invoice_payment(invoice_id,payment_entry_id), FOREIGN KEY(invoice_id) REFERENCES finance_invoices(id),
 FOREIGN KEY(payment_entry_id) REFERENCES finance_ledger_entries(id), FOREIGN KEY(allocated_by) REFERENCES users(id), CHECK(amount>0)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE finance_credit_notes (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, invoice_id BIGINT UNSIGNED NOT NULL, issuer_scope_key VARCHAR(80) NOT NULL,
 credit_number VARCHAR(60) NOT NULL, amount DECIMAL(12,2) NOT NULL, reason VARCHAR(500) NOT NULL,
 ledger_entry_id BIGINT UNSIGNED NOT NULL, snapshot_cipher LONGTEXT NOT NULL, snapshot_hash CHAR(64) NOT NULL,
 issued_by BIGINT UNSIGNED NOT NULL, issued_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 UNIQUE KEY uq_credit_scope_number(issuer_scope_key,credit_number), KEY idx_credit_invoice(invoice_id,id),
 FOREIGN KEY(invoice_id) REFERENCES finance_invoices(id), FOREIGN KEY(ledger_entry_id) REFERENCES finance_ledger_entries(id),
 FOREIGN KEY(issued_by) REFERENCES users(id), CHECK(amount>0)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

ALTER TABLE outbound_mail_queue ADD invoice_id BIGINT UNSIGNED NULL AFTER booking_id,
 ADD KEY idx_mail_invoice(invoice_id,status), ADD CONSTRAINT fk_mail_invoice FOREIGN KEY(invoice_id) REFERENCES finance_invoices(id) ON DELETE SET NULL;

CREATE TABLE finance_invoice_mailings (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, invoice_id BIGINT UNSIGNED NOT NULL, mail_queue_id BIGINT UNSIGNED NOT NULL,
 recipient VARCHAR(254) NOT NULL, queued_by BIGINT UNSIGNED NOT NULL, queued_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 UNIQUE KEY uq_invoice_mail_queue(mail_queue_id), FOREIGN KEY(invoice_id) REFERENCES finance_invoices(id),
 FOREIGN KEY(mail_queue_id) REFERENCES outbound_mail_queue(id), FOREIGN KEY(queued_by) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE finance_invoice_events (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, invoice_id BIGINT UNSIGNED NOT NULL, event_key VARCHAR(50) NOT NULL,
 actor_id BIGINT UNSIGNED NOT NULL, details_json VARCHAR(1000) NULL, occurred_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 KEY idx_invoice_event(invoice_id,id), FOREIGN KEY(invoice_id) REFERENCES finance_invoices(id), FOREIGN KEY(actor_id) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TRIGGER invoice_snapshot_no_update BEFORE UPDATE ON finance_invoice_issue_snapshots FOR EACH ROW SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Issued invoice snapshots are immutable';
CREATE TRIGGER invoice_snapshot_no_delete BEFORE DELETE ON finance_invoice_issue_snapshots FOR EACH ROW SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Issued invoice snapshots are immutable';
CREATE TRIGGER credit_note_no_update BEFORE UPDATE ON finance_credit_notes FOR EACH ROW SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Credit notes are immutable';
CREATE TRIGGER credit_note_no_delete BEFORE DELETE ON finance_credit_notes FOR EACH ROW SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Credit notes are immutable';

INSERT INTO migration_history(version,name,checksum,execution_ms)
VALUES('202609280040','Versioned invoices issuer numbering payments and credit notes',SHA2('202609280040_invoices_v1',256),0);
COMMIT;

SET NAMES utf8mb4;
START TRANSACTION;

INSERT INTO tenant_roles(role_key,display_name,built_in,active) VALUES
('almoner','Almoner',1,1),('province_almoner','Province Almoner',1,1)
ON DUPLICATE KEY UPDATE display_name=VALUES(display_name),active=1;
INSERT IGNORE INTO tenant_role_scopes(role_key,scope_type) VALUES('almoner','unit'),('province_almoner','province');

CREATE TABLE welfare_scope_settings (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, scope_key VARCHAR(80) NOT NULL, scope_type ENUM('unit','province') NOT NULL,
 province_id BIGINT UNSIGNED NOT NULL, unit_id BIGINT UNSIGNED NULL, enabled TINYINT(1) NOT NULL DEFAULT 0,
 legal_basis VARCHAR(255) NOT NULL, special_category_condition VARCHAR(255) NULL, health_information_enabled TINYINT(1) NOT NULL DEFAULT 0,
 retention_months SMALLINT UNSIGNED NOT NULL DEFAULT 24, approved_by BIGINT UNSIGNED NOT NULL, approved_at DATETIME NOT NULL,
 updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 UNIQUE KEY uq_welfare_scope(scope_key), KEY idx_welfare_mandate(province_id,scope_type,enabled),
 FOREIGN KEY(province_id) REFERENCES provinces(id), FOREIGN KEY(unit_id) REFERENCES units(id), FOREIGN KEY(approved_by) REFERENCES users(id),
 CHECK(retention_months BETWEEN 1 AND 120)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE welfare_cases (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, scope_key VARCHAR(80) NOT NULL, scope_type ENUM('unit','province') NOT NULL,
 province_id BIGINT UNSIGNED NOT NULL, unit_id BIGINT UNSIGNED NULL, person_id BIGINT UNSIGNED NOT NULL,
 assigned_user_id BIGINT UNSIGNED NOT NULL, status ENUM('open','review','closed','withdrawn','retired') NOT NULL DEFAULT 'open',
 personal_summary_cipher LONGTEXT NULL, working_notes_cipher LONGTEXT NULL, contains_health_information TINYINT(1) NOT NULL DEFAULT 0,
 legal_basis_snapshot VARCHAR(255) NOT NULL, special_condition_snapshot VARCHAR(255) NULL,
 opened_on DATE NOT NULL, next_review_on DATE NULL, retention_review_due DATE NOT NULL,
 withdrawn_at DATETIME NULL, retired_at DATETIME NULL, created_by BIGINT UNSIGNED NOT NULL,
 created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 KEY idx_welfare_case_queue(scope_key,assigned_user_id,status,next_review_on), KEY idx_welfare_case_person(person_id,scope_key),
 FOREIGN KEY(province_id) REFERENCES provinces(id), FOREIGN KEY(unit_id) REFERENCES units(id), FOREIGN KEY(person_id) REFERENCES people(id),
 FOREIGN KEY(assigned_user_id) REFERENCES users(id), FOREIGN KEY(created_by) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE welfare_case_assignments (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, case_id BIGINT UNSIGNED NOT NULL, assigned_user_id BIGINT UNSIGNED NOT NULL,
 assigned_by BIGINT UNSIGNED NOT NULL, assigned_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, ended_at DATETIME NULL,
 KEY idx_welfare_assignment(case_id,ended_at), FOREIGN KEY(case_id) REFERENCES welfare_cases(id),
 FOREIGN KEY(assigned_user_id) REFERENCES users(id), FOREIGN KEY(assigned_by) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE welfare_contacts (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, case_id BIGINT UNSIGNED NOT NULL,
 contact_type ENUM('phone','email','letter','visit','message','other') NOT NULL, occurred_at DATETIME NOT NULL,
 private_note_cipher LONGTEXT NULL, agreed_follow_up_cipher LONGTEXT NULL, follow_up_due DATETIME NULL,
 completed_at DATETIME NULL, created_by BIGINT UNSIGNED NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 KEY idx_welfare_followup(case_id,follow_up_due,completed_at), FOREIGN KEY(case_id) REFERENCES welfare_cases(id), FOREIGN KEY(created_by) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE welfare_life_events (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, case_id BIGINT UNSIGNED NOT NULL,
 event_type ENUM('bereavement','illness','hospital','recovery','death','care','other') NOT NULL, event_date DATE NOT NULL,
 private_detail_cipher LONGTEXT NULL, created_by BIGINT UNSIGNED NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 KEY idx_welfare_life_event(case_id,event_date), FOREIGN KEY(case_id) REFERENCES welfare_cases(id), FOREIGN KEY(created_by) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE welfare_related_contacts (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, scope_key VARCHAR(80) NOT NULL, member_person_id BIGINT UNSIGNED NOT NULL,
 related_person_id BIGINT UNSIGNED NULL, relationship_type ENUM('spouse','partner','surviving_spouse','surviving_partner','other') NOT NULL,
 display_name_cipher LONGTEXT NOT NULL, email_cipher LONGTEXT NULL, phone_cipher LONGTEXT NULL, address_cipher LONGTEXT NULL,
 preferred_channel ENUM('email','phone','post','none') NOT NULL DEFAULT 'none', contact_allowed TINYINT(1) NOT NULL DEFAULT 0,
 preference_source VARCHAR(255) NOT NULL, preference_recorded_at DATETIME NOT NULL, withdrawn_at DATETIME NULL,
 created_by BIGINT UNSIGNED NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 KEY idx_welfare_relation(scope_key,member_person_id,withdrawn_at), FOREIGN KEY(member_person_id) REFERENCES people(id),
 FOREIGN KEY(related_person_id) REFERENCES people(id) ON DELETE SET NULL, FOREIGN KEY(created_by) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE welfare_gestures (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, case_id BIGINT UNSIGNED NOT NULL,
 gesture_type ENUM('card','flowers','visit','cheque','support','reimbursement') NOT NULL, planned_for DATE NOT NULL,
 status ENUM('planned','approved','sent','completed','cancelled','rejected','paid') NOT NULL DEFAULT 'planned',
 amount DECIMAL(12,2) NULL, accounting_category VARCHAR(80) NULL, private_reason_cipher LONGTEXT NULL,
 finance_entry_id BIGINT UNSIGNED NULL, created_by BIGINT UNSIGNED NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 KEY idx_welfare_gesture(case_id,status,planned_for), KEY idx_welfare_finance(status,accounting_category,planned_for),
 FOREIGN KEY(case_id) REFERENCES welfare_cases(id), FOREIGN KEY(finance_entry_id) REFERENCES finance_ledger_entries(id), FOREIGN KEY(created_by) REFERENCES users(id),
 CHECK((gesture_type IN('cheque','support','reimbursement') AND amount IS NOT NULL AND amount>0 AND accounting_category IS NOT NULL) OR (gesture_type NOT IN('cheque','support','reimbursement') AND amount IS NULL))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE welfare_exceptional_access (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, scope_key VARCHAR(80) NOT NULL, user_id BIGINT UNSIGNED NOT NULL,
 granted_by BIGINT UNSIGNED NOT NULL, reason_cipher LONGTEXT NOT NULL, granted_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 expires_at DATETIME NOT NULL, revoked_at DATETIME NULL, last_used_at DATETIME NULL,
 KEY idx_welfare_exception(scope_key,user_id,expires_at,revoked_at), FOREIGN KEY(user_id) REFERENCES users(id), FOREIGN KEY(granted_by) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE welfare_case_events (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, case_id BIGINT UNSIGNED NULL, scope_key VARCHAR(80) NOT NULL,
 actor_id BIGINT UNSIGNED NOT NULL, event_key VARCHAR(80) NOT NULL, occurred_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 KEY idx_welfare_event(scope_key,occurred_at), FOREIGN KEY(case_id) REFERENCES welfare_cases(id) ON DELETE SET NULL, FOREIGN KEY(actor_id) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

ALTER TABLE tenant_retention_schedules MODIFY data_class ENUM('memberships','candidates','guest_directory','bookings','guests','messages','documents','welfare','finance','dc_private_notes') NOT NULL;
INSERT INTO migration_history(version,name,checksum,execution_ms) VALUES('202609280041','Scoped Almoner welfare cases and restricted finance sharing',SHA2('202609280041_almoner_welfare_v1',256),0);
COMMIT;

SET NAMES utf8mb4;
START TRANSACTION;

ALTER TABLE people ADD birth_date DATE NULL AFTER phone, ADD KEY idx_people_birth_date(birth_date);

CREATE TABLE report_catalogue (
 report_key VARCHAR(60) PRIMARY KEY, title VARCHAR(160) NOT NULL, scope_levels SET('unit','province') NOT NULL,
 required_capability VARCHAR(80) NOT NULL, audience VARCHAR(160) NOT NULL, definition_text VARCHAR(1000) NOT NULL,
 source_text VARCHAR(1000) NOT NULL, small_group_threshold TINYINT UNSIGNED NOT NULL DEFAULT 5, active TINYINT(1) NOT NULL DEFAULT 1,
 CHECK(small_group_threshold BETWEEN 3 AND 20)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE report_schedules (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, scope_key VARCHAR(80) NOT NULL, scope_type ENUM('unit','province') NOT NULL,
 province_id BIGINT UNSIGNED NOT NULL, unit_id BIGINT UNSIGNED NULL, report_key VARCHAR(60) NOT NULL,
 filters_json VARCHAR(2000) NOT NULL, output_format ENUM('html','csv','pdf') NOT NULL DEFAULT 'html',
 recipient_type ENUM('user','role') NOT NULL, recipient_user_id BIGINT UNSIGNED NULL, recipient_role VARCHAR(60) NULL,
 cadence ENUM('daily','weekly','monthly') NOT NULL, next_run_at DATETIME NOT NULL, expires_hours SMALLINT UNSIGNED NOT NULL DEFAULT 168,
 active TINYINT(1) NOT NULL DEFAULT 1, created_by BIGINT UNSIGNED NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 KEY idx_report_schedule_due(active,next_run_at,id), KEY idx_report_schedule_scope(scope_key,report_key),
 FOREIGN KEY(report_key) REFERENCES report_catalogue(report_key), FOREIGN KEY(province_id) REFERENCES provinces(id),
 FOREIGN KEY(unit_id) REFERENCES units(id), FOREIGN KEY(recipient_user_id) REFERENCES users(id) ON DELETE SET NULL,
 FOREIGN KEY(created_by) REFERENCES users(id),
 CHECK((scope_type='unit' AND unit_id IS NOT NULL) OR (scope_type='province' AND unit_id IS NULL)),
 CHECK((recipient_type='user' AND recipient_user_id IS NOT NULL AND recipient_role IS NULL) OR (recipient_type='role' AND recipient_user_id IS NULL AND recipient_role IS NOT NULL)),
 CHECK(expires_hours BETWEEN 1 AND 720)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE report_runs (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, schedule_id BIGINT UNSIGNED NULL, scope_key VARCHAR(80) NOT NULL,
 scope_type ENUM('unit','province') NOT NULL, province_id BIGINT UNSIGNED NOT NULL, unit_id BIGINT UNSIGNED NULL,
 report_key VARCHAR(60) NOT NULL, filters_json VARCHAR(2000) NOT NULL, definition_snapshot VARCHAR(1000) NOT NULL,
 source_snapshot VARCHAR(1000) NOT NULL, as_of DATETIME NOT NULL, output_format ENUM('html','csv','pdf') NOT NULL,
 payload_cipher LONGTEXT NOT NULL, row_count INT UNSIGNED NOT NULL DEFAULT 0, status ENUM('ready','expired','revoked') NOT NULL DEFAULT 'ready',
 idempotency_key CHAR(64) NOT NULL, expires_at DATETIME NOT NULL, generated_by BIGINT UNSIGNED NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 UNIQUE KEY uq_report_run_idempotency(idempotency_key), KEY idx_report_run_scope(scope_key,created_at), KEY idx_report_run_expiry(status,expires_at),
 FOREIGN KEY(schedule_id) REFERENCES report_schedules(id) ON DELETE SET NULL, FOREIGN KEY(report_key) REFERENCES report_catalogue(report_key),
 FOREIGN KEY(province_id) REFERENCES provinces(id), FOREIGN KEY(unit_id) REFERENCES units(id), FOREIGN KEY(generated_by) REFERENCES users(id),
 CHECK((scope_type='unit' AND unit_id IS NOT NULL) OR (scope_type='province' AND unit_id IS NULL))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE report_run_recipients (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, report_run_id BIGINT UNSIGNED NOT NULL, user_id BIGINT UNSIGNED NOT NULL,
 required_capability VARCHAR(80) NOT NULL, notified_at DATETIME NULL, opened_at DATETIME NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 UNIQUE KEY uq_report_recipient(report_run_id,user_id), KEY idx_report_inbox(user_id,created_at),
 FOREIGN KEY(report_run_id) REFERENCES report_runs(id) ON DELETE CASCADE, FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE report_events (
 id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, report_run_id BIGINT UNSIGNED NOT NULL, actor_id BIGINT UNSIGNED NOT NULL,
 event_key VARCHAR(60) NOT NULL, occurred_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 KEY idx_report_event(report_run_id,occurred_at), FOREIGN KEY(report_run_id) REFERENCES report_runs(id) ON DELETE CASCADE,
 FOREIGN KEY(actor_id) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

INSERT INTO report_catalogue(report_key,title,scope_levels,required_capability,audience,definition_text,source_text,small_group_threshold) VALUES
('age_bands','Age bands','unit,province','reports.membership','Secretary and authorised Province membership officers','Current unit memberships grouped by age at the as-of date; missing birth dates are Unknown.','people.birth_date and unit_members; membership state is recorded explicitly.',5),
('membership_trends','Membership trends and movements','unit,province','reports.membership','Secretary and authorised Province membership officers','Starts and ends in the selected period, plus current membership and verified-person totals.','unit_members dates and verified people identities.',5),
('member_status','Active and inactive membership','unit,province','reports.membership','Secretary and authorised Province membership officers','Counts the explicitly recorded active, inactive, resigned and deceased states. Attendance never changes these states.','unit_members.state.',5),
('membership_type','Membership type','unit,province','reports.membership','Secretary and authorised Province membership officers','Current unit memberships grouped by recorded membership type.','unit_members.membership_type.',5),
('admission_route','Admitted and joining members','unit,province','reports.ceremonies','Secretary and authorised Province ceremonial officers','Unit memberships grouped by the explicitly recorded admitted or joining route.','unit_members.admission_route.',5),
('ceremony_progression','Order ceremony progression','unit,province','reports.ceremonies','Secretary and authorised Province ceremonial officers','Recorded ceremony milestones grouped by Order and configured terminology.','member_milestones joined to unit_members.',5),
('office_succession','Office and succession','unit,province','reports.ceremonies','Secretary and authorised Province ceremonial officers','Recorded office terms overlapping the selected period; no future succession is inferred.','member_offices joined to unit_members.',5),
('birthdays_anniversaries','Birthdays and Masonic anniversaries','unit,province','reports.membership','Secretary and authorised Province membership officers','Known birthdays and recorded membership start anniversaries in the selected month. Province output is aggregate only.','people.birth_date and unit_members.start_date.',5),
('attendance','Attendance, apologies, visitors and no-shows','unit,province','reports.attendance','Secretary and authorised Province operational officers','Booking and kiosk outcomes by event. A no-show is a non-apology active booking without check-in after the event; it does not explain membership status.','meetings, bookings and guests kiosk fields.',5),
('event_capacity','Event and dining capacity','unit,province','reports.dining','Dining Steward and authorised Province operational officers','Active booking and dining-place counts compared with recorded event capacity.','meetings, bookings and guests.',5),
('communications_delivery','Communications delivery','unit,province','reports.communications','Secretary and authorised Province communications officers','Queued, sent and failed delivery counts in the selected period; message content and recipients are excluded.','communication_log status and timestamps.',5);

INSERT INTO migration_history(version,name,checksum,execution_ms) VALUES('202609280042','Scoped report catalogue inbox schedules and privacy-safe aggregates',SHA2('202609280042_report_catalogue_v1',256),0);
COMMIT;

SELECT 'BookingSystem clean schema installed successfully' AS install_status,
       (SELECT COUNT(*) FROM information_schema.tables WHERE table_schema=DATABASE() AND table_type='BASE TABLE') AS table_count,
       (SELECT COUNT(*) FROM migration_history) AS migration_count,
       (SELECT COUNT(*) FROM users) AS user_count,
       (SELECT COUNT(*) FROM units) AS unit_count;
