-- MJ Marketplace MySQL/MariaDB compatible safe schema helpers.
-- Required because some MySQL versions do not support ALTER TABLE ... ADD COLUMN.
DROP PROCEDURE IF EXISTS mj_safe_add_column;
DROP PROCEDURE IF EXISTS mj_safe_add_index;
DELIMITER $$
CREATE PROCEDURE mj_safe_add_column(IN p_table VARCHAR(128), IN p_col VARCHAR(128), IN p_def TEXT)
BEGIN
  IF EXISTS (SELECT 1 FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = p_table LIMIT 1)
     AND NOT EXISTS (SELECT 1 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = p_table AND COLUMN_NAME = p_col LIMIT 1) THEN
    SET @mj_sql = CONCAT('ALTER TABLE `', REPLACE(p_table,'`','``'), '` ADD COLUMN ', p_def);
    PREPARE mj_stmt FROM @mj_sql;
    EXECUTE mj_stmt;
    DEALLOCATE PREPARE mj_stmt;
  END IF;
END$$
CREATE PROCEDURE mj_safe_add_index(IN p_table VARCHAR(128), IN p_index VARCHAR(128), IN p_def TEXT)
BEGIN
  IF EXISTS (SELECT 1 FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = p_table LIMIT 1)
     AND NOT EXISTS (SELECT 1 FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = p_table AND INDEX_NAME = p_index LIMIT 1) THEN
    SET @mj_sql = CONCAT('ALTER TABLE `', REPLACE(p_table,'`','``'), '` ', p_def);
    PREPARE mj_stmt FROM @mj_sql;
    EXECUTE mj_stmt;
    DEALLOCATE PREPARE mj_stmt;
  END IF;
END$$
DELIMITER ;

-- ============================================================
-- MJ Marketplace Global Pro v3.0.4.1 STABLE FRESH INSTALL SQL
-- Combined schema + all migrations up to v3.0.4.1 only.
-- v3.0.5 location importer/live-city experimental files are intentionally excluded.
-- Locked rule: customers do not pay sellers; users/sellers pay admin only.
-- Generated: 2026-06-13 07:45:08
-- ============================================================

SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET time_zone = "+08:00";
SET FOREIGN_KEY_CHECKS = 0;



-- ============================================================
-- Source: install/schema.sql
-- ============================================================

SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET time_zone = "+00:00";
CREATE TABLE IF NOT EXISTS mj_settings (
  id INT AUTO_INCREMENT PRIMARY KEY,
  setting_key VARCHAR(120) NOT NULL UNIQUE,
  setting_value TEXT NULL,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_countries (
  id INT AUTO_INCREMENT PRIMARY KEY,
  iso2 CHAR(2) NOT NULL UNIQUE,
  iso3 VARCHAR(3) NULL,
  name VARCHAR(160) NOT NULL,
  slug VARCHAR(180) NOT NULL UNIQUE,
  flag_emoji VARCHAR(16) NULL,
  currency_code VARCHAR(8) DEFAULT 'USD',
  phone_code VARCHAR(20) NULL,
  timezone_default VARCHAR(80) DEFAULT 'UTC',
  language_default VARCHAR(20) DEFAULT 'en',
  tax_name VARCHAR(40) NULL,
  tax_rate DECIMAL(8,3) DEFAULT 0,
  seo_title VARCHAR(255) NULL,
  seo_description TEXT NULL,
  is_active TINYINT(1) DEFAULT 0,
  sort_order INT DEFAULT 0,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  INDEX idx_country_active (is_active), INDEX idx_country_slug (slug), INDEX idx_country_iso3 (iso3)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_states (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  country_id INT NOT NULL,
  name VARCHAR(180) NOT NULL,
  state_code VARCHAR(50) NULL,
  slug VARCHAR(200) NOT NULL,
  timezone VARCHAR(80) NULL,
  latitude DECIMAL(12,8) NULL,
  longitude DECIMAL(12,8) NULL,
  is_active TINYINT(1) DEFAULT 1,
  UNIQUE KEY uniq_state (country_id, slug), INDEX idx_state_country(country_id),
  CONSTRAINT fk_state_country FOREIGN KEY(country_id) REFERENCES mj_countries(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_cities (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  country_id INT NOT NULL,
  state_id BIGINT NULL,
  name VARCHAR(180) NOT NULL,
  slug VARCHAR(200) NOT NULL,
  timezone VARCHAR(80) NULL,
  latitude DECIMAL(12,8) NULL,
  longitude DECIMAL(12,8) NULL,
  is_active TINYINT(1) DEFAULT 1,
  INDEX idx_city_country(country_id), INDEX idx_city_state(state_id), INDEX idx_city_slug(slug),
  CONSTRAINT fk_city_country FOREIGN KEY(country_id) REFERENCES mj_countries(id) ON DELETE CASCADE,
  CONSTRAINT fk_city_state FOREIGN KEY(state_id) REFERENCES mj_states(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_users (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(160) NOT NULL,
  email VARCHAR(190) NOT NULL UNIQUE,
  mobile VARCHAR(50) NULL,
  password_hash VARCHAR(255) NOT NULL,
  avatar VARCHAR(255) NULL,
  country_id INT NULL,
  state_id BIGINT NULL,
  city_id BIGINT NULL,
  timezone VARCHAR(80) DEFAULT 'UTC',
  preferred_currency VARCHAR(8) DEFAULT 'USD',
  trust_score INT DEFAULT 0,
  is_email_verified TINYINT(1) DEFAULT 0,
  is_mobile_verified TINYINT(1) DEFAULT 0,
  is_active TINYINT(1) DEFAULT 1,
  last_login_at DATETIME NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  INDEX idx_users_country(country_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_admin_users (
  id INT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(120) NOT NULL,
  email VARCHAR(190) NOT NULL UNIQUE,
  password_hash VARCHAR(255) NOT NULL,
  role VARCHAR(60) DEFAULT 'super_admin',
  country_id INT NULL,
  is_active TINYINT(1) DEFAULT 1,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_categories (
  id INT AUTO_INCREMENT PRIMARY KEY,
  parent_id INT NULL,
  type ENUM('marketplace','company','job','property','vehicle','service','course','event') DEFAULT 'marketplace',
  name VARCHAR(160) NOT NULL,
  slug VARCHAR(180) NOT NULL,
  icon VARCHAR(80) NULL,
  requires_approval TINYINT(1) DEFAULT 0,
  custom_fields_json JSON NULL,
  is_active TINYINT(1) DEFAULT 1,
  sort_order INT DEFAULT 0,
  UNIQUE KEY uniq_cat(type,slug), INDEX idx_cat_parent(parent_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_listings (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  company_id BIGINT NULL,
  branch_id BIGINT NULL,
  category_id INT NULL,
  country_id INT NOT NULL,
  state_id BIGINT NULL,
  city_id BIGINT NULL,
  timezone VARCHAR(80) DEFAULT 'UTC',
  listing_type ENUM('product','service','rental','auction','job','property','vehicle','course','event') DEFAULT 'product',
  title VARCHAR(220) NOT NULL,
  slug VARCHAR(240) NOT NULL,
  description MEDIUMTEXT NULL,
  price DECIMAL(12,2) DEFAULT 0,
  currency_code VARCHAR(8) DEFAULT 'USD',
  condition_label VARCHAR(80) NULL,
  brand VARCHAR(120) NULL,
  model VARCHAR(120) NULL,
  status ENUM('draft','pending','active','reserved','sold','expired','rejected') DEFAULT 'pending',
  is_featured TINYINT(1) DEFAULT 0,
  is_urgent TINYINT(1) DEFAULT 0,
  views BIGINT DEFAULT 0,
  quality_score INT DEFAULT 0,
  expires_at DATETIME NULL,
  published_at DATETIME NULL,
  custom_fields_json JSON NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  INDEX idx_listing_country(country_id,status), INDEX idx_listing_cat(category_id), INDEX idx_listing_city(city_id), FULLTEXT KEY ft_listing(title,description)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_listing_images (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  listing_id BIGINT NOT NULL,
  image_path VARCHAR(255) NOT NULL,
  sort_order INT DEFAULT 0,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  CONSTRAINT fk_li_listing FOREIGN KEY(listing_id) REFERENCES mj_listings(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_companies (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  category_id INT NULL,
  country_id INT NOT NULL,
  state_id BIGINT NULL,
  city_id BIGINT NULL,
  timezone VARCHAR(80) DEFAULT 'UTC',
  name VARCHAR(220) NOT NULL,
  slug VARCHAR(240) NOT NULL,
  registration_no VARCHAR(120) NULL,
  logo VARCHAR(255) NULL,
  cover_image VARCHAR(255) NULL,
  description MEDIUMTEXT NULL,
  phone VARCHAR(80) NULL,
  whatsapp VARCHAR(80) NULL,
  email VARCHAR(190) NULL,
  website VARCHAR(255) NULL,
  address TEXT NULL,
  latitude DECIMAL(12,8) NULL,
  longitude DECIMAL(12,8) NULL,
  status ENUM('draft','pending','active','rejected','suspended') DEFAULT 'pending',
  is_verified TINYINT(1) DEFAULT 0,
  verification_status VARCHAR(40) NOT NULL DEFAULT 'not_verified',
  verification_until DATE NULL,
  is_featured TINYINT(1) DEFAULT 0,
  is_sponsored TINYINT(1) NOT NULL DEFAULT 0,
  sponsored_until DATETIME NULL,
  rating_avg DECIMAL(4,2) DEFAULT 0,
  rating_count INT DEFAULT 0,
  trust_score INT NOT NULL DEFAULT 0,
  profile_score INT NOT NULL DEFAULT 0,
  views BIGINT DEFAULT 0,
  admin_note TEXT NULL,
  rejection_reason TEXT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  last_reviewed_at DATETIME NULL,
  last_reviewed_by INT NULL,
  submitted_at DATETIME NULL,
  approved_at DATETIME NULL,
  approved_by BIGINT NULL,
  INDEX idx_company_country(country_id,status), FULLTEXT KEY ft_company(name,description,address)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_company_branches (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  company_id BIGINT NOT NULL,
  country_id INT NOT NULL,
  state_id BIGINT NULL,
  city_id BIGINT NULL,
  name VARCHAR(180) NOT NULL,
  timezone VARCHAR(80) DEFAULT 'UTC',
  phone VARCHAR(80) NULL,
  whatsapp VARCHAR(80) NULL,
  email VARCHAR(190) NULL,
  address TEXT NULL,
  opening_hours_json JSON NULL,
  is_main TINYINT(1) DEFAULT 0,
  is_active TINYINT(1) DEFAULT 1,
  CONSTRAINT fk_branch_company FOREIGN KEY(company_id) REFERENCES mj_companies(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_company_services (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  company_id BIGINT NOT NULL,
  branch_id BIGINT NULL,
  name VARCHAR(180) NOT NULL,
  price_from DECIMAL(12,2) DEFAULT 0,
  duration_minutes INT DEFAULT 0,
  description TEXT NULL,
  is_bookable TINYINT(1) DEFAULT 0,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  CONSTRAINT fk_cs_company FOREIGN KEY(company_id) REFERENCES mj_companies(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_company_timeline (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  company_id BIGINT NOT NULL,
  actor_type VARCHAR(30) NOT NULL DEFAULT 'system',
  actor_id BIGINT NULL,
  action VARCHAR(120) NOT NULL,
  note TEXT NULL,
  old_status VARCHAR(40) NULL,
  new_status VARCHAR(40) NULL,
  ip_address VARCHAR(80) NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_company_timeline_company(company_id),
  INDEX idx_company_timeline_action(action)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;


CREATE TABLE IF NOT EXISTS mj_company_seed (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  country_id INT NOT NULL,
  state_id BIGINT NULL,
  city_id BIGINT NULL,
  source VARCHAR(80) DEFAULT 'csv',
  source_ref VARCHAR(190) NULL,
  uen VARCHAR(120) NULL,
  entity_name VARCHAR(255) NOT NULL,
  entity_type VARCHAR(160) NULL,
  entity_status VARCHAR(120) NULL,
  registration_date DATE NULL,
  primary_activity TEXT NULL,
  secondary_activity TEXT NULL,
  address_text TEXT NULL,
  postal_code VARCHAR(30) NULL,
  phone VARCHAR(80) NULL,
  email VARCHAR(190) NULL,
  website VARCHAR(255) NULL,
  services TEXT NULL,
  category_text VARCHAR(190) NULL,
  latitude DECIMAL(12,8) NULL,
  longitude DECIMAL(12,8) NULL,
  timezone VARCHAR(80) DEFAULT 'UTC',
  is_claimed TINYINT(1) DEFAULT 0,
  claimed_company_id BIGINT NULL,
  raw_json JSON NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  UNIQUE KEY uniq_seed_country_source (country_id, source, source_ref, uen, entity_name),
  INDEX idx_seed_country(country_id),
  INDEX idx_seed_city(city_id),
  FULLTEXT KEY ft_seed(entity_name,address_text,services,category_text)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_company_claims (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  seed_id BIGINT NULL,
  company_id BIGINT NULL,
  user_id BIGINT NOT NULL,
  claim_name VARCHAR(220) NOT NULL,
  claim_email VARCHAR(190) NULL,
  claim_phone VARCHAR(80) NULL,
  proof_path VARCHAR(255) NULL,
  status ENUM('pending','approved','rejected') DEFAULT 'pending',
  admin_notes TEXT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  INDEX idx_claim_status(status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_chats (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  listing_id BIGINT NULL,
  company_id BIGINT NULL,
  buyer_id BIGINT NOT NULL,
  seller_id BIGINT NOT NULL,
  last_message TEXT NULL,
  last_message_at DATETIME NULL,
  status ENUM('active','archived','reported','blocked') DEFAULT 'active',
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  UNIQUE KEY uniq_chat(listing_id,company_id,buyer_id,seller_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_chat_messages (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  chat_id BIGINT NOT NULL,
  sender_id BIGINT NOT NULL,
  message TEXT NOT NULL,
  attachment_path VARCHAR(255) NULL,
  risk_score INT DEFAULT 0,
  is_read TINYINT(1) DEFAULT 0,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_msg_chat(chat_id), CONSTRAINT fk_msg_chat FOREIGN KEY(chat_id) REFERENCES mj_chats(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_offers (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  listing_id BIGINT NOT NULL,
  buyer_id BIGINT NOT NULL,
  seller_id BIGINT NOT NULL,
  amount DECIMAL(12,2) NOT NULL,
  currency_code VARCHAR(8) DEFAULT 'USD',
  status ENUM('pending','accepted','rejected','countered','cancelled','expired') DEFAULT 'pending',
  expires_at DATETIME NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_orders (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  listing_id BIGINT NULL,
  company_id BIGINT NULL,
  offer_id BIGINT NULL,
  buyer_id BIGINT NOT NULL,
  seller_id BIGINT NOT NULL,
  country_id INT NOT NULL,
  amount DECIMAL(12,2) NOT NULL,
  tax_amount DECIMAL(12,2) DEFAULT 0,
  currency_code VARCHAR(8) DEFAULT 'USD',
  status ENUM('pending_payment','paid','processing','completed','cancelled','disputed','refunded') DEFAULT 'pending_payment',
  delivery_method VARCHAR(80) NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_payments (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  order_id BIGINT NULL,
  user_id BIGINT NOT NULL,
  method VARCHAR(80) NOT NULL,
  amount DECIMAL(12,2) NOT NULL,
  currency_code VARCHAR(8) DEFAULT 'USD',
  proof_path VARCHAR(255) NULL,
  gateway_ref VARCHAR(190) NULL,
  status ENUM('pending','approved','rejected','refunded') DEFAULT 'pending',
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_reviews (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  order_id BIGINT NULL,
  listing_id BIGINT NULL,
  company_id BIGINT NULL,
  branch_id BIGINT NULL,
  reviewer_id BIGINT NOT NULL,
  target_user_id BIGINT NULL,
  rating TINYINT NOT NULL,
  review_text TEXT NULL,
  reply_text TEXT NULL,
  is_verified_purchase TINYINT(1) DEFAULT 0,
  status ENUM('pending','active','hidden','reported') DEFAULT 'active',
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_reports (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  reporter_id BIGINT NULL,
  target_type VARCHAR(60) NOT NULL,
  target_id BIGINT NOT NULL,
  reason VARCHAR(160) NOT NULL,
  details TEXT NULL,
  status ENUM('new','reviewing','resolved','dismissed') DEFAULT 'new',
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_notifications (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  title VARCHAR(180) NOT NULL,
  body TEXT NULL,
  url VARCHAR(255) NULL,
  is_read TINYINT(1) DEFAULT 0,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_quotations (id BIGINT AUTO_INCREMENT PRIMARY KEY, company_id BIGINT NOT NULL, user_id BIGINT NOT NULL, subject VARCHAR(220), amount DECIMAL(12,2) DEFAULT 0, currency_code VARCHAR(8) DEFAULT 'USD', status VARCHAR(40) DEFAULT 'draft', valid_until DATETIME NULL, details MEDIUMTEXT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_rfq_requests (id BIGINT AUTO_INCREMENT PRIMARY KEY, user_id BIGINT NOT NULL, country_id INT NOT NULL, category_id INT NULL, title VARCHAR(220), budget DECIMAL(12,2) DEFAULT 0, details MEDIUMTEXT NULL, status VARCHAR(40) DEFAULT 'open', created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_bookings (id BIGINT AUTO_INCREMENT PRIMARY KEY, company_id BIGINT NOT NULL, branch_id BIGINT NULL, service_id BIGINT NULL, user_id BIGINT NOT NULL, start_at DATETIME NOT NULL, end_at DATETIME NULL, timezone VARCHAR(80) DEFAULT 'UTC', status VARCHAR(40) DEFAULT 'pending', created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_wallets (id BIGINT AUTO_INCREMENT PRIMARY KEY, user_id BIGINT NOT NULL UNIQUE, balance DECIMAL(12,2) DEFAULT 0, currency_code VARCHAR(8) DEFAULT 'USD', updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_wallet_transactions (id BIGINT AUTO_INCREMENT PRIMARY KEY, wallet_id BIGINT NOT NULL, type VARCHAR(50), amount DECIMAL(12,2), reference_type VARCHAR(80), reference_id BIGINT, notes TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_boost_packages (id INT AUTO_INCREMENT PRIMARY KEY, country_id INT NULL, package_type VARCHAR(80), name VARCHAR(160), price DECIMAL(12,2) DEFAULT 0, currency_code VARCHAR(8) DEFAULT 'USD', duration_days INT DEFAULT 7, is_active TINYINT(1) DEFAULT 1) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_subscriptions (id BIGINT AUTO_INCREMENT PRIMARY KEY, user_id BIGINT NOT NULL, company_id BIGINT NULL, plan_name VARCHAR(120), status VARCHAR(40) DEFAULT 'active', starts_at DATETIME, ends_at DATETIME, created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_banners (id BIGINT AUTO_INCREMENT PRIMARY KEY, country_id INT NULL, city_id BIGINT NULL, placement VARCHAR(80), title VARCHAR(180), image_path VARCHAR(255), link_url VARCHAR(255), starts_at DATETIME NULL, ends_at DATETIME NULL, is_active TINYINT(1) DEFAULT 1) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_pages (id BIGINT AUTO_INCREMENT PRIMARY KEY, slug VARCHAR(180) UNIQUE, title VARCHAR(220), body MEDIUMTEXT, seo_title VARCHAR(255), seo_description TEXT, is_active TINYINT(1) DEFAULT 1, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_email_templates (id INT AUTO_INCREMENT PRIMARY KEY, template_key VARCHAR(120) UNIQUE, subject VARCHAR(220), body MEDIUMTEXT, is_active TINYINT(1) DEFAULT 1) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_site_modules (id INT AUTO_INCREMENT PRIMARY KEY, module_code VARCHAR(80) UNIQUE, module_name VARCHAR(160), description TEXT, is_enabled TINYINT(1) DEFAULT 1) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_fraud_rules (id INT AUTO_INCREMENT PRIMARY KEY, rule_type VARCHAR(80), keyword VARCHAR(190), risk_points INT DEFAULT 10, is_active TINYINT(1) DEFAULT 1) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_support_tickets (id BIGINT AUTO_INCREMENT PRIMARY KEY, user_id BIGINT NULL, subject VARCHAR(220), category VARCHAR(80), status VARCHAR(40) DEFAULT 'open', message TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_admin_logs (id BIGINT AUTO_INCREMENT PRIMARY KEY, admin_id INT NULL, action VARCHAR(120), table_name VARCHAR(120), row_id BIGINT NULL, data_json JSON NULL, ip_address VARCHAR(80), created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_import_logs (id BIGINT AUTO_INCREMENT PRIMARY KEY, import_type VARCHAR(80), filename VARCHAR(255), total_rows INT DEFAULT 0, success_rows INT DEFAULT 0, failed_rows INT DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_cron_logs (id BIGINT AUTO_INCREMENT PRIMARY KEY, job_name VARCHAR(120), status VARCHAR(40), message TEXT, started_at DATETIME, ended_at DATETIME) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

INSERT IGNORE INTO mj_settings(setting_key,setting_value) VALUES
('site_name','MJ Marketplace'),('site_description','Buy, sell, chat and deal safely worldwide.'),('default_country_slug','singapore'),('theme_color','#0f172a'),('quiet_hours_start','22:00'),('quiet_hours_end','08:00'),('listing_approval_required','1'),('company_approval_required','1');

INSERT IGNORE INTO mj_countries(iso2,name,slug,flag_emoji,currency_code,timezone_default,is_active) VALUES
('AF','Afghanistan','afghanistan','🇦🇫','AFN','Asia/Kabul',0),
('AL','Albania','albania','🇦🇱','ALL','Europe/Tirane',0),
('DZ','Algeria','algeria','🇩🇿','DZD','Africa/Algiers',0),
('AS','American Samoa','american-samoa','🇦🇸','USD','Pacific/Pago_Pago',0),
('AD','Andorra','andorra','🇦🇩','EUR','Europe/Andorra',0),
('AO','Angola','angola','🇦🇴','AOA','Africa/Luanda',0),
('AI','Anguilla','anguilla','🇦🇮','XCD','America/Anguilla',0),
('AQ','Antarctica','antarctica','🇦🇶','USD','Antarctica/McMurdo',0),
('AG','Antigua and Barbuda','antigua-and-barbuda','🇦🇬','XCD','America/Antigua',0),
('AR','Argentina','argentina','🇦🇷','ARS','America/Argentina/Buenos_Aires',0),
('AM','Armenia','armenia','🇦🇲','AMD','Asia/Yerevan',0),
('AW','Aruba','aruba','🇦🇼','AWG','America/Aruba',0),
('AU','Australia','australia','🇦🇺','AUD','Australia/Lord_Howe',1),
('AT','Austria','austria','🇦🇹','EUR','Europe/Vienna',0),
('AZ','Azerbaijan','azerbaijan','🇦🇿','AZN','Asia/Baku',0),
('BS','Bahamas','bahamas','🇧🇸','BSD','America/Nassau',0),
('BH','Bahrain','bahrain','🇧🇭','BHD','Asia/Bahrain',0),
('BD','Bangladesh','bangladesh','🇧🇩','BDT','Asia/Dhaka',0),
('BB','Barbados','barbados','🇧🇧','BBD','America/Barbados',0),
('BY','Belarus','belarus','🇧🇾','BYN','Europe/Minsk',0),
('BE','Belgium','belgium','🇧🇪','EUR','Europe/Brussels',0),
('BZ','Belize','belize','🇧🇿','BZD','America/Belize',0),
('BJ','Benin','benin','🇧🇯','XOF','Africa/Porto-Novo',0),
('BM','Bermuda','bermuda','🇧🇲','BMD','Atlantic/Bermuda',0),
('BT','Bhutan','bhutan','🇧🇹','INR','Asia/Thimphu',0),
('BO','Bolivia','bolivia','🇧🇴','BOB','America/La_Paz',0),
('BQ','Bonaire, Sint Eustatius and Saba','bonaire-sint-eustatius-and-saba','🇧🇶','USD','America/Kralendijk',0),
('BA','Bosnia and Herzegovina','bosnia-and-herzegovina','🇧🇦','BAM','Europe/Sarajevo',0),
('BW','Botswana','botswana','🇧🇼','BWP','Africa/Gaborone',0),
('BV','Bouvet Island','bouvet-island','🇧🇻','NOK','UTC',0),
('BR','Brazil','brazil','🇧🇷','BRL','America/Noronha',0),
('IO','British Indian Ocean Territory','british-indian-ocean-territory','🇮🇴','USD','Indian/Chagos',0),
('BN','Brunei Darussalam','brunei-darussalam','🇧🇳','BND','Asia/Brunei',0),
('BG','Bulgaria','bulgaria','🇧🇬','BGN','Europe/Sofia',0),
('BF','Burkina Faso','burkina-faso','🇧🇫','XOF','Africa/Ouagadougou',0),
('BI','Burundi','burundi','🇧🇮','BIF','Africa/Bujumbura',0),
('CV','Cabo Verde','cabo-verde','🇨🇻','CVE','Atlantic/Cape_Verde',0),
('KH','Cambodia','cambodia','🇰🇭','KHR','Asia/Phnom_Penh',0),
('CM','Cameroon','cameroon','🇨🇲','XAF','Africa/Douala',0),
('CA','Canada','canada','🇨🇦','CAD','America/St_Johns',0),
('KY','Cayman Islands','cayman-islands','🇰🇾','KYD','America/Cayman',0),
('CF','Central African Republic','central-african-republic','🇨🇫','XAF','Africa/Bangui',0),
('TD','Chad','chad','🇹🇩','XAF','Africa/Ndjamena',0),
('CL','Chile','chile','🇨🇱','CLP','America/Santiago',0),
('CN','China','china','🇨🇳','CNY','Asia/Shanghai',0),
('CX','Christmas Island','christmas-island','🇨🇽','AUD','Indian/Christmas',0),
('CC','Cocos (Keeling) Islands','cocos-keeling-islands','🇨🇨','AUD','Indian/Cocos',0),
('CO','Colombia','colombia','🇨🇴','COP','America/Bogota',0),
('KM','Comoros','comoros','🇰🇲','KMF','Indian/Comoro',0),
('CG','Congo','congo','🇨🇬','XAF','Africa/Brazzaville',0),
('CD','Congo, The Democratic Republic of the','congo-the-democratic-republic-of-the','🇨🇩','CDF','Africa/Kinshasa',0),
('CK','Cook Islands','cook-islands','🇨🇰','NZD','Pacific/Rarotonga',0),
('CR','Costa Rica','costa-rica','🇨🇷','CRC','America/Costa_Rica',0),
('HR','Croatia','croatia','🇭🇷','EUR','Europe/Zagreb',0),
('CU','Cuba','cuba','🇨🇺','CUP','America/Havana',0),
('CW','Curaçao','cura-ao','🇨🇼','XCG','America/Curacao',0),
('CY','Cyprus','cyprus','🇨🇾','EUR','Asia/Nicosia',0),
('CZ','Czechia','czechia','🇨🇿','CZK','Europe/Prague',0),
('CI','Côte d''Ivoire','c-te-d-ivoire','🇨🇮','XOF','Africa/Abidjan',0),
('DK','Denmark','denmark','🇩🇰','DKK','Europe/Copenhagen',0),
('DJ','Djibouti','djibouti','🇩🇯','DJF','Africa/Djibouti',0),
('DM','Dominica','dominica','🇩🇲','XCD','America/Dominica',0),
('DO','Dominican Republic','dominican-republic','🇩🇴','DOP','America/Santo_Domingo',0),
('EC','Ecuador','ecuador','🇪🇨','USD','America/Guayaquil',0),
('EG','Egypt','egypt','🇪🇬','EGP','Africa/Cairo',0),
('SV','El Salvador','el-salvador','🇸🇻','USD','America/El_Salvador',0),
('GQ','Equatorial Guinea','equatorial-guinea','🇬🇶','XAF','Africa/Malabo',0),
('ER','Eritrea','eritrea','🇪🇷','ERN','Africa/Asmara',0),
('EE','Estonia','estonia','🇪🇪','EUR','Europe/Tallinn',0),
('SZ','Eswatini','eswatini','🇸🇿','SZL','Africa/Mbabane',0),
('ET','Ethiopia','ethiopia','🇪🇹','ETB','Africa/Addis_Ababa',0),
('FK','Falkland Islands (Malvinas)','falkland-islands-malvinas','🇫🇰','FKP','Atlantic/Stanley',0),
('FO','Faroe Islands','faroe-islands','🇫🇴','DKK','Atlantic/Faroe',0),
('FJ','Fiji','fiji','🇫🇯','FJD','Pacific/Fiji',0),
('FI','Finland','finland','🇫🇮','EUR','Europe/Helsinki',0),
('FR','France','france','🇫🇷','EUR','Europe/Paris',0),
('GF','French Guiana','french-guiana','🇬🇫','EUR','America/Cayenne',0),
('PF','French Polynesia','french-polynesia','🇵🇫','XPF','Pacific/Tahiti',0),
('TF','French Southern Territories','french-southern-territories','🇹🇫','EUR','Indian/Kerguelen',0),
('GA','Gabon','gabon','🇬🇦','XAF','Africa/Libreville',0),
('GM','Gambia','gambia','🇬🇲','GMD','Africa/Banjul',0),
('GE','Georgia','georgia','🇬🇪','GEL','Asia/Tbilisi',0),
('DE','Germany','germany','🇩🇪','EUR','Europe/Berlin',0),
('GH','Ghana','ghana','🇬🇭','GHS','Africa/Accra',0),
('GI','Gibraltar','gibraltar','🇬🇮','GIP','Europe/Gibraltar',0),
('GR','Greece','greece','🇬🇷','EUR','Europe/Athens',0),
('GL','Greenland','greenland','🇬🇱','DKK','America/Nuuk',0),
('GD','Grenada','grenada','🇬🇩','XCD','America/Grenada',0),
('GP','Guadeloupe','guadeloupe','🇬🇵','EUR','America/Guadeloupe',0),
('GU','Guam','guam','🇬🇺','USD','Pacific/Guam',0),
('GT','Guatemala','guatemala','🇬🇹','GTQ','America/Guatemala',0),
('GG','Guernsey','guernsey','🇬🇬','GBP','Europe/Guernsey',0),
('GN','Guinea','guinea','🇬🇳','GNF','Africa/Conakry',0),
('GW','Guinea-Bissau','guinea-bissau','🇬🇼','XOF','Africa/Bissau',0),
('GY','Guyana','guyana','🇬🇾','GYD','America/Guyana',0),
('HT','Haiti','haiti','🇭🇹','HTG','America/Port-au-Prince',0),
('HM','Heard Island and McDonald Islands','heard-island-and-mcdonald-islands','🇭🇲','AUD','UTC',0),
('VA','Holy See (Vatican City State)','holy-see-vatican-city-state','🇻🇦','EUR','Europe/Vatican',0),
('HN','Honduras','honduras','🇭🇳','HNL','America/Tegucigalpa',0),
('HK','Hong Kong','hong-kong','🇭🇰','HKD','Asia/Hong_Kong',0),
('HU','Hungary','hungary','🇭🇺','HUF','Europe/Budapest',0),
('IS','Iceland','iceland','🇮🇸','ISK','Atlantic/Reykjavik',0),
('IN','India','india','🇮🇳','INR','Asia/Kolkata',1),
('ID','Indonesia','indonesia','🇮🇩','IDR','Asia/Jakarta',1),
('IR','Iran','iran','🇮🇷','IRR','Asia/Tehran',0),
('IQ','Iraq','iraq','🇮🇶','IQD','Asia/Baghdad',0),
('IE','Ireland','ireland','🇮🇪','EUR','Europe/Dublin',0),
('IM','Isle of Man','isle-of-man','🇮🇲','GBP','Europe/Isle_of_Man',0),
('IL','Israel','israel','🇮🇱','ILS','Asia/Jerusalem',0),
('IT','Italy','italy','🇮🇹','EUR','Europe/Rome',0),
('JM','Jamaica','jamaica','🇯🇲','JMD','America/Jamaica',0),
('JP','Japan','japan','🇯🇵','JPY','Asia/Tokyo',0),
('JE','Jersey','jersey','🇯🇪','GBP','Europe/Jersey',0),
('JO','Jordan','jordan','🇯🇴','JOD','Asia/Amman',0),
('KZ','Kazakhstan','kazakhstan','🇰🇿','KZT','Asia/Almaty',0),
('KE','Kenya','kenya','🇰🇪','KES','Africa/Nairobi',0),
('KI','Kiribati','kiribati','🇰🇮','AUD','Pacific/Tarawa',0),
('KP','North Korea','north-korea','🇰🇵','KPW','Asia/Pyongyang',0),
('KR','South Korea','south-korea','🇰🇷','KRW','Asia/Seoul',0),
('KW','Kuwait','kuwait','🇰🇼','KWD','Asia/Kuwait',0),
('KG','Kyrgyzstan','kyrgyzstan','🇰🇬','KGS','Asia/Bishkek',0),
('LA','Laos','laos','🇱🇦','LAK','Asia/Vientiane',0),
('LV','Latvia','latvia','🇱🇻','EUR','Europe/Riga',0),
('LB','Lebanon','lebanon','🇱🇧','LBP','Asia/Beirut',0),
('LS','Lesotho','lesotho','🇱🇸','ZAR','Africa/Maseru',0),
('LR','Liberia','liberia','🇱🇷','LRD','Africa/Monrovia',0),
('LY','Libya','libya','🇱🇾','LYD','Africa/Tripoli',0),
('LI','Liechtenstein','liechtenstein','🇱🇮','CHF','Europe/Vaduz',0),
('LT','Lithuania','lithuania','🇱🇹','EUR','Europe/Vilnius',0),
('LU','Luxembourg','luxembourg','🇱🇺','EUR','Europe/Luxembourg',0),
('MO','Macao','macao','🇲🇴','MOP','Asia/Macau',0),
('MG','Madagascar','madagascar','🇲🇬','MGA','Indian/Antananarivo',0),
('MW','Malawi','malawi','🇲🇼','MWK','Africa/Blantyre',0),
('MY','Malaysia','malaysia','🇲🇾','MYR','Asia/Kuala_Lumpur',1),
('MV','Maldives','maldives','🇲🇻','MVR','Indian/Maldives',0),
('ML','Mali','mali','🇲🇱','XOF','Africa/Bamako',0),
('MT','Malta','malta','🇲🇹','EUR','Europe/Malta',0),
('MH','Marshall Islands','marshall-islands','🇲🇭','USD','Pacific/Majuro',0),
('MQ','Martinique','martinique','🇲🇶','EUR','America/Martinique',0),
('MR','Mauritania','mauritania','🇲🇷','MRU','Africa/Nouakchott',0),
('MU','Mauritius','mauritius','🇲🇺','MUR','Indian/Mauritius',0),
('YT','Mayotte','mayotte','🇾🇹','EUR','Indian/Mayotte',0),
('MX','Mexico','mexico','🇲🇽','MXN','America/Mexico_City',0),
('FM','Micronesia, Federated States of','micronesia-federated-states-of','🇫🇲','USD','Pacific/Chuuk',0),
('MD','Moldova','moldova','🇲🇩','MDL','Europe/Chisinau',0),
('MC','Monaco','monaco','🇲🇨','EUR','Europe/Monaco',0),
('MN','Mongolia','mongolia','🇲🇳','MNT','Asia/Ulaanbaatar',0),
('ME','Montenegro','montenegro','🇲🇪','EUR','Europe/Podgorica',0),
('MS','Montserrat','montserrat','🇲🇸','XCD','America/Montserrat',0),
('MA','Morocco','morocco','🇲🇦','MAD','Africa/Casablanca',0),
('MZ','Mozambique','mozambique','🇲🇿','MZN','Africa/Maputo',0),
('MM','Myanmar','myanmar','🇲🇲','MMK','Asia/Yangon',0),
('NA','Namibia','namibia','🇳🇦','ZAR','Africa/Windhoek',0),
('NR','Nauru','nauru','🇳🇷','AUD','Pacific/Nauru',0),
('NP','Nepal','nepal','🇳🇵','NPR','Asia/Kathmandu',0),
('NL','Netherlands','netherlands','🇳🇱','EUR','Europe/Amsterdam',0),
('NC','New Caledonia','new-caledonia','🇳🇨','XPF','Pacific/Noumea',0),
('NZ','New Zealand','new-zealand','🇳🇿','NZD','Pacific/Auckland',0),
('NI','Nicaragua','nicaragua','🇳🇮','NIO','America/Managua',0),
('NE','Niger','niger','🇳🇪','XOF','Africa/Niamey',0),
('NG','Nigeria','nigeria','🇳🇬','NGN','Africa/Lagos',0),
('NU','Niue','niue','🇳🇺','NZD','Pacific/Niue',0),
('NF','Norfolk Island','norfolk-island','🇳🇫','AUD','Pacific/Norfolk',0),
('MK','North Macedonia','north-macedonia','🇲🇰','MKD','Europe/Skopje',0),
('MP','Northern Mariana Islands','northern-mariana-islands','🇲🇵','USD','Pacific/Saipan',0),
('NO','Norway','norway','🇳🇴','NOK','Europe/Oslo',0),
('OM','Oman','oman','🇴🇲','OMR','Asia/Muscat',0),
('PK','Pakistan','pakistan','🇵🇰','PKR','Asia/Karachi',0),
('PW','Palau','palau','🇵🇼','USD','Pacific/Palau',0),
('PS','Palestine, State of','palestine-state-of','🇵🇸','ILS','Asia/Gaza',0),
('PA','Panama','panama','🇵🇦','PAB','America/Panama',0),
('PG','Papua New Guinea','papua-new-guinea','🇵🇬','PGK','Pacific/Port_Moresby',0),
('PY','Paraguay','paraguay','🇵🇾','PYG','America/Asuncion',0),
('PE','Peru','peru','🇵🇪','PEN','America/Lima',0),
('PH','Philippines','philippines','🇵🇭','PHP','Asia/Manila',1),
('PN','Pitcairn','pitcairn','🇵🇳','NZD','Pacific/Pitcairn',0),
('PL','Poland','poland','🇵🇱','PLN','Europe/Warsaw',0),
('PT','Portugal','portugal','🇵🇹','EUR','Europe/Lisbon',0),
('PR','Puerto Rico','puerto-rico','🇵🇷','USD','America/Puerto_Rico',0),
('QA','Qatar','qatar','🇶🇦','QAR','Asia/Qatar',0),
('RO','Romania','romania','🇷🇴','RON','Europe/Bucharest',0),
('RU','Russian Federation','russian-federation','🇷🇺','RUB','Europe/Kaliningrad',0),
('RW','Rwanda','rwanda','🇷🇼','RWF','Africa/Kigali',0),
('RE','Réunion','r-union','🇷🇪','EUR','Indian/Reunion',0),
('BL','Saint Barthélemy','saint-barth-lemy','🇧🇱','EUR','America/St_Barthelemy',0),
('SH','Saint Helena, Ascension and Tristan da Cunha','saint-helena-ascension-and-tristan-da-cunha','🇸🇭','SHP','Atlantic/St_Helena',0),
('KN','Saint Kitts and Nevis','saint-kitts-and-nevis','🇰🇳','XCD','America/St_Kitts',0),
('LC','Saint Lucia','saint-lucia','🇱🇨','XCD','America/St_Lucia',0),
('MF','Saint Martin (French part)','saint-martin-french-part','🇲🇫','EUR','America/Marigot',0),
('PM','Saint Pierre and Miquelon','saint-pierre-and-miquelon','🇵🇲','EUR','America/Miquelon',0),
('VC','Saint Vincent and the Grenadines','saint-vincent-and-the-grenadines','🇻🇨','XCD','America/St_Vincent',0),
('WS','Samoa','samoa','🇼🇸','WST','Pacific/Apia',0),
('SM','San Marino','san-marino','🇸🇲','EUR','Europe/San_Marino',0),
('ST','Sao Tome and Principe','sao-tome-and-principe','🇸🇹','STN','Africa/Sao_Tome',0),
('SA','Saudi Arabia','saudi-arabia','🇸🇦','SAR','Asia/Riyadh',0),
('SN','Senegal','senegal','🇸🇳','XOF','Africa/Dakar',0),
('RS','Serbia','serbia','🇷🇸','RSD','Europe/Belgrade',0),
('SC','Seychelles','seychelles','🇸🇨','SCR','Indian/Mahe',0),
('SL','Sierra Leone','sierra-leone','🇸🇱','SLE','Africa/Freetown',0),
('SG','Singapore','singapore','🇸🇬','SGD','Asia/Singapore',1),
('SX','Sint Maarten (Dutch part)','sint-maarten-dutch-part','🇸🇽','XCG','America/Lower_Princes',0),
('SK','Slovakia','slovakia','🇸🇰','EUR','Europe/Bratislava',0),
('SI','Slovenia','slovenia','🇸🇮','EUR','Europe/Ljubljana',0),
('SB','Solomon Islands','solomon-islands','🇸🇧','SBD','Pacific/Guadalcanal',0),
('SO','Somalia','somalia','🇸🇴','SOS','Africa/Mogadishu',0),
('ZA','South Africa','south-africa','🇿🇦','ZAR','Africa/Johannesburg',0),
('GS','South Georgia and the South Sandwich Islands','south-georgia-and-the-south-sandwich-islands','🇬🇸','GBP','Atlantic/South_Georgia',0),
('SS','South Sudan','south-sudan','🇸🇸','SSP','Africa/Juba',0),
('ES','Spain','spain','🇪🇸','EUR','Europe/Madrid',0),
('LK','Sri Lanka','sri-lanka','🇱🇰','LKR','Asia/Colombo',0),
('SD','Sudan','sudan','🇸🇩','SDG','Africa/Khartoum',0),
('SR','Suriname','suriname','🇸🇷','SRD','America/Paramaribo',0),
('SJ','Svalbard and Jan Mayen','svalbard-and-jan-mayen','🇸🇯','NOK','Arctic/Longyearbyen',0),
('SE','Sweden','sweden','🇸🇪','SEK','Europe/Stockholm',0),
('CH','Switzerland','switzerland','🇨🇭','CHF','Europe/Zurich',0),
('SY','Syria','syria','🇸🇾','SYP','Asia/Damascus',0),
('TW','Taiwan','taiwan','🇹🇼','TWD','Asia/Taipei',0),
('TJ','Tajikistan','tajikistan','🇹🇯','TJS','Asia/Dushanbe',0),
('TZ','Tanzania','tanzania','🇹🇿','TZS','Africa/Dar_es_Salaam',0),
('TH','Thailand','thailand','🇹🇭','THB','Asia/Bangkok',1),
('TL','Timor-Leste','timor-leste','🇹🇱','USD','Asia/Dili',0),
('TG','Togo','togo','🇹🇬','XOF','Africa/Lome',0),
('TK','Tokelau','tokelau','🇹🇰','NZD','Pacific/Fakaofo',0),
('TO','Tonga','tonga','🇹🇴','TOP','Pacific/Tongatapu',0),
('TT','Trinidad and Tobago','trinidad-and-tobago','🇹🇹','TTD','America/Port_of_Spain',0),
('TN','Tunisia','tunisia','🇹🇳','TND','Africa/Tunis',0),
('TM','Turkmenistan','turkmenistan','🇹🇲','TMT','Asia/Ashgabat',0),
('TC','Turks and Caicos Islands','turks-and-caicos-islands','🇹🇨','USD','America/Grand_Turk',0),
('TV','Tuvalu','tuvalu','🇹🇻','AUD','Pacific/Funafuti',0),
('TR','Türkiye','t-rkiye','🇹🇷','TRY','Europe/Istanbul',0),
('UG','Uganda','uganda','🇺🇬','UGX','Africa/Kampala',0),
('UA','Ukraine','ukraine','🇺🇦','UAH','Europe/Simferopol',0),
('AE','United Arab Emirates','united-arab-emirates','🇦🇪','AED','Asia/Dubai',1),
('GB','United Kingdom','united-kingdom','🇬🇧','GBP','Europe/London',1),
('US','United States','united-states','🇺🇸','USD','America/New_York',1),
('UM','United States Minor Outlying Islands','united-states-minor-outlying-islands','🇺🇲','USD','Pacific/Midway',0),
('UY','Uruguay','uruguay','🇺🇾','UYU','America/Montevideo',0),
('UZ','Uzbekistan','uzbekistan','🇺🇿','UZS','Asia/Samarkand',0),
('VU','Vanuatu','vanuatu','🇻🇺','VUV','Pacific/Efate',0),
('VE','Venezuela','venezuela','🇻🇪','VES','America/Caracas',0),
('VN','Vietnam','vietnam','🇻🇳','VND','Asia/Ho_Chi_Minh',0),
('VG','Virgin Islands, British','virgin-islands-british','🇻🇬','USD','America/Tortola',0),
('VI','Virgin Islands, U.S.','virgin-islands-u-s','🇻🇮','USD','America/St_Thomas',0),
('WF','Wallis and Futuna','wallis-and-futuna','🇼🇫','XPF','Pacific/Wallis',0),
('EH','Western Sahara','western-sahara','🇪🇭','MAD','Africa/El_Aaiun',0),
('YE','Yemen','yemen','🇾🇪','YER','Asia/Aden',0),
('ZM','Zambia','zambia','🇿🇲','ZMW','Africa/Lusaka',0),
('ZW','Zimbabwe','zimbabwe','🇿🇼','USD','Africa/Harare',0),
('AX','Åland Islands','land-islands','🇦🇽','EUR','Europe/Mariehamn',0);


INSERT IGNORE INTO mj_categories(type,name,slug,icon,sort_order) VALUES
('marketplace','Mobile Phones','mobile-phones','📱',1),('marketplace','Electronics','electronics','💻',2),('marketplace','Computers & Laptops','computers-laptops','🖥️',3),('marketplace','Home & Furniture','home-furniture','🛋️',4),('marketplace','Fashion','fashion','👕',5),('marketplace','Cars','cars','🚗',6),('marketplace','Motorbikes','motorbikes','🏍️',7),('marketplace','Property','property','🏠',8),('marketplace','Jobs','jobs','💼',9),('marketplace','Services','services','🛠️',10),('marketplace','Data Recovery','data-recovery','💾',11),('marketplace','CCTV Service','cctv-service','📹',12),('marketplace','Others','others','✨',99),
('company','IT Services','it-services','💻',1),('company','Data Recovery','data-recovery','💾',2),('company','Home Services','home-services','🏠',3),('company','Automotive','automotive','🚗',4),('company','Medical & Beauty','medical-beauty','💉',5),('company','Education','education','🎓',6),('company','Food & Beverage','food-beverage','🍽️',7),('company','Property Services','property-services','🏢',8),('company','Other Businesses','other-businesses','✨',99);

INSERT IGNORE INTO mj_site_modules(module_code,module_name,description,is_enabled) VALUES
('import_center','Import Center','CSV import for listings, companies, business seed, users, categories and services',1),
('multi_country','Multi-country marketplaces','Standalone country URLs, currencies and timezones',1),('state_city','State and city filters','Country > State > City location system',1),('company_directory','Company directory','Business listings, branches, hours and enquiries',1),('chat','Marketplace chat','Buyer/seller and company chat',1),('offers','Make offer','Accept, reject, counter and expire offers',1),('orders','Orders and checkout','Buy now, order flow and manual payment proof',1),('wallet','Wallet and credits','Internal credits, refunds and boost balance',1),('boosts','Promoted listings','Featured listings, bump and ads',1),('rfq','Request for quotation','Buyers request quotes from companies',1),('booking','Service booking','Book services by local timezone',1),('reviews','Reviews and ratings','Verified purchase/company reviews',1),('fraud','Fraud detection','Rules, risk points and scam warnings',1),('seo','Advanced SEO','Sitemaps, schema and SEO pages',1),('pwa','PWA mobile app','Installable mobile-friendly web app',1),('crm','Company CRM','Lead status, quotations, follow-up',1),('subscriptions','Seller/company packages','Free/pro/premium subscriptions',1),('api','API and webhooks','Future mobile app and integrations',1),('backups','Backup tools','Database and upload backup manager',1),('dark_mode','Dark mode','Optional theme preference',1),('ai_helper','AI helpers placeholder','AI title, description and SEO suggestions',0);

INSERT IGNORE INTO mj_fraud_rules(rule_type,keyword,risk_points) VALUES
('chat_keyword','otp',50),('chat_keyword','bank login',60),('chat_keyword','singpass',70),('chat_keyword','crypto',40),('chat_keyword','outside platform',20),('chat_keyword','pay first',20),('external_link','bit.ly',25),('external_link','tinyurl',25);

-- Advanced admin, user account and area-priority tables
CREATE TABLE IF NOT EXISTS mj_visitor_locations (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  country_id INT NULL,
  state_id BIGINT NULL,
  city_id BIGINT NULL,
  country_iso2 CHAR(2) NULL,
  country_name VARCHAR(160) NULL,
  state_name VARCHAR(180) NULL,
  city_name VARCHAR(180) NULL,
  timezone VARCHAR(80) NULL,
  latitude DECIMAL(12,8) NULL,
  longitude DECIMAL(12,8) NULL,
  ip_address VARCHAR(80) NULL,
  user_agent VARCHAR(255) NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_visitor_country(country_id), INDEX idx_visitor_city(city_id), INDEX idx_visitor_created(created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_saved_listings (id BIGINT AUTO_INCREMENT PRIMARY KEY,user_id BIGINT NOT NULL,listing_id BIGINT NOT NULL,created_at DATETIME DEFAULT CURRENT_TIMESTAMP,UNIQUE KEY uniq_saved_listing(user_id,listing_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_saved_companies (id BIGINT AUTO_INCREMENT PRIMARY KEY,user_id BIGINT NOT NULL,company_id BIGINT NOT NULL,created_at DATETIME DEFAULT CURRENT_TIMESTAMP,UNIQUE KEY uniq_saved_company(user_id,company_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_saved_searches (id BIGINT AUTO_INCREMENT PRIMARY KEY,user_id BIGINT NOT NULL,title VARCHAR(180) NOT NULL,search_url VARCHAR(255) NOT NULL,alert_enabled TINYINT(1) DEFAULT 0,created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_user_devices (id BIGINT AUTO_INCREMENT PRIMARY KEY,user_id BIGINT NOT NULL,ip_address VARCHAR(80) NULL,user_agent VARCHAR(255) NULL,last_seen_at DATETIME DEFAULT CURRENT_TIMESTAMP,created_at DATETIME DEFAULT CURRENT_TIMESTAMP,INDEX idx_device_user(user_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_user_privacy_requests (id BIGINT AUTO_INCREMENT PRIMARY KEY,user_id BIGINT NOT NULL,request_type VARCHAR(80) NOT NULL,status VARCHAR(40) DEFAULT 'pending',notes TEXT NULL,created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_search_logs (id BIGINT AUTO_INCREMENT PRIMARY KEY,user_id BIGINT NULL,country_id INT NULL,keyword VARCHAR(190) NULL,result_type VARCHAR(40) NULL,ip_address VARCHAR(80) NULL,created_at DATETIME DEFAULT CURRENT_TIMESTAMP,INDEX idx_search_country(country_id),INDEX idx_search_keyword(keyword)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT IGNORE INTO mj_settings(setting_key,setting_value) VALUES ('auto_area_priority','1'),('allow_ip_area_detection','1'),('maintenance_mode','0'),('super_admin_login_as_user','1'),('default_nearby_sort','city_state_country_featured');


/* ===== MJ Marketplace Ultimate Modules ===== */
CREATE TABLE IF NOT EXISTS mj_admin_alerts (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  alert_key VARCHAR(191) NOT NULL UNIQUE,
  alert_type VARCHAR(80) NOT NULL,
  module_name VARCHAR(80) DEFAULT NULL,
  priority ENUM('low','normal','high','urgent','critical') DEFAULT 'normal',
  title VARCHAR(255) NOT NULL,
  message TEXT NULL,
  action_url VARCHAR(500) DEFAULT NULL,
  action_label VARCHAR(100) DEFAULT NULL,
  country_id INT DEFAULT NULL,
  city_id BIGINT DEFAULT NULL,
  related_type VARCHAR(80) DEFAULT NULL,
  related_id BIGINT DEFAULT NULL,
  target_role VARCHAR(80) DEFAULT NULL,
  is_read TINYINT(1) DEFAULT 0,
  read_at DATETIME DEFAULT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  expires_at DATETIME DEFAULT NULL,
  INDEX(alert_type), INDEX(priority), INDEX(is_read), INDEX(created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_error_logs (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  error_hash VARCHAR(191) DEFAULT NULL,
  error_type VARCHAR(80) NOT NULL,
  error_level VARCHAR(80) DEFAULT NULL,
  error_message TEXT NULL,
  error_file VARCHAR(500) DEFAULT NULL,
  error_line INT DEFAULT NULL,
  request_url TEXT NULL,
  request_method VARCHAR(20) DEFAULT NULL,
  referrer_url TEXT NULL,
  user_id BIGINT DEFAULT NULL,
  admin_id INT DEFAULT NULL,
  ip_address VARCHAR(80) DEFAULT NULL,
  user_agent TEXT NULL,
  post_data LONGTEXT NULL,
  get_data LONGTEXT NULL,
  session_data LONGTEXT NULL,
  stack_trace LONGTEXT NULL,
  status ENUM('new','reviewing','fixed','ignored','reopened') DEFAULT 'new',
  priority ENUM('low','normal','high','urgent','critical') DEFAULT 'high',
  occurrence_count INT DEFAULT 1,
  first_seen_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  last_seen_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  fixed_at DATETIME DEFAULT NULL,
  fixed_by INT DEFAULT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX(error_hash), INDEX(error_type), INDEX(status), INDEX(priority), INDEX(created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_admin_alert_rules (id INT AUTO_INCREMENT PRIMARY KEY, rule_name VARCHAR(160), alert_type VARCHAR(80), target_role VARCHAR(80), country_id INT NULL, is_enabled TINYINT(1) DEFAULT 1, settings_json JSON NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_notification_templates (id INT AUTO_INCREMENT PRIMARY KEY, template_key VARCHAR(120) UNIQUE, channel VARCHAR(40) DEFAULT 'in_app', title VARCHAR(255), body TEXT, variables_json JSON NULL, is_active TINYINT(1) DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_notification_rules (id INT AUTO_INCREMENT PRIMARY KEY, rule_key VARCHAR(120) UNIQUE, rule_name VARCHAR(160), event_type VARCHAR(100), channel VARCHAR(40), timing_rule VARCHAR(100), is_enabled TINYINT(1) DEFAULT 1, settings_json JSON NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_notifications (id BIGINT AUTO_INCREMENT PRIMARY KEY, user_id BIGINT NULL, company_id BIGINT NULL, admin_id INT NULL, country_id INT NULL, city_id BIGINT NULL, type VARCHAR(80), title VARCHAR(255), message TEXT, icon VARCHAR(80), priority VARCHAR(40) DEFAULT 'normal', action_url VARCHAR(500), is_read TINYINT(1) DEFAULT 0, read_at DATETIME NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, scheduled_at DATETIME NULL, expires_at DATETIME NULL, INDEX(user_id,is_read), INDEX(type), INDEX(created_at)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_notifications (fresh installer safe).
CALL mj_safe_add_column('mj_notifications', 'user_id', '`user_id` BIGINT NULL');
CALL mj_safe_add_column('mj_notifications', 'company_id', '`company_id` BIGINT NULL');
CALL mj_safe_add_column('mj_notifications', 'admin_id', '`admin_id` INT NULL');
CALL mj_safe_add_column('mj_notifications', 'country_id', '`country_id` INT NULL');
CALL mj_safe_add_column('mj_notifications', 'city_id', '`city_id` BIGINT NULL');
CALL mj_safe_add_column('mj_notifications', 'type', '`type` VARCHAR(80)');
CALL mj_safe_add_column('mj_notifications', 'title', '`title` VARCHAR(255)');
CALL mj_safe_add_column('mj_notifications', 'message', '`message` TEXT');
CALL mj_safe_add_column('mj_notifications', 'icon', '`icon` VARCHAR(80)');
CALL mj_safe_add_column('mj_notifications', 'priority', '`priority` VARCHAR(40) DEFAULT ''normal''');
CALL mj_safe_add_column('mj_notifications', 'action_url', '`action_url` VARCHAR(500)');
CALL mj_safe_add_column('mj_notifications', 'is_read', '`is_read` TINYINT(1) DEFAULT 0');
CALL mj_safe_add_column('mj_notifications', 'read_at', '`read_at` DATETIME NULL');
CALL mj_safe_add_column('mj_notifications', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');
CALL mj_safe_add_column('mj_notifications', 'scheduled_at', '`scheduled_at` DATETIME NULL');
CALL mj_safe_add_column('mj_notifications', 'expires_at', '`expires_at` DATETIME NULL');

CREATE TABLE IF NOT EXISTS mj_social_providers (id INT AUTO_INCREMENT PRIMARY KEY, provider VARCHAR(50) UNIQUE, client_id TEXT NULL, client_secret TEXT NULL, redirect_url TEXT NULL, is_enabled TINYINT(1) DEFAULT 0, settings_json JSON NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_user_social_accounts (id BIGINT AUTO_INCREMENT PRIMARY KEY, user_id BIGINT NOT NULL, provider VARCHAR(50) NOT NULL, provider_user_id VARCHAR(191) NOT NULL, provider_email VARCHAR(191), provider_name VARCHAR(191), provider_avatar TEXT, access_token TEXT, refresh_token TEXT, token_expires_at DATETIME NULL, linked_at DATETIME DEFAULT CURRENT_TIMESTAMP, last_login_at DATETIME NULL, UNIQUE KEY unique_provider_user(provider, provider_user_id), INDEX(user_id), INDEX(provider_email)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_category_fields (id BIGINT AUTO_INCREMENT PRIMARY KEY, category_id INT NULL, applies_to VARCHAR(40) DEFAULT 'listing', field_key VARCHAR(120), field_label VARCHAR(190), field_type VARCHAR(50), is_required TINYINT(1) DEFAULT 0, is_searchable TINYINT(1) DEFAULT 0, show_on_card TINYINT(1) DEFAULT 0, sort_order INT DEFAULT 0, help_text TEXT NULL, validation_json JSON NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX(category_id), INDEX(applies_to)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_category_field_options (id BIGINT AUTO_INCREMENT PRIMARY KEY, field_id BIGINT NOT NULL, option_label VARCHAR(180), option_value VARCHAR(180), sort_order INT DEFAULT 0, INDEX(field_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_dynamic_field_values (id BIGINT AUTO_INCREMENT PRIMARY KEY, related_type VARCHAR(40), related_id BIGINT, field_id BIGINT, field_key VARCHAR(120), field_value TEXT, INDEX(related_type, related_id), INDEX(field_key)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_seo_pages (id BIGINT AUTO_INCREMENT PRIMARY KEY, page_type VARCHAR(80), related_type VARCHAR(80), related_id BIGINT NULL, country_id INT NULL, city_id BIGINT NULL, slug VARCHAR(255), meta_title VARCHAR(255), meta_description TEXT, h1 VARCHAR(255), content_top MEDIUMTEXT, content_bottom MEDIUMTEXT, canonical_url TEXT, og_image VARCHAR(255), noindex TINYINT(1) DEFAULT 0, is_active TINYINT(1) DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX(page_type), INDEX(slug), INDEX(country_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_seo_redirects (id BIGINT AUTO_INCREMENT PRIMARY KEY, old_url VARCHAR(500), new_url VARCHAR(500), status_code INT DEFAULT 301, hit_count BIGINT DEFAULT 0, is_active TINYINT(1) DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX(old_url(191))) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_sitemap_logs (id BIGINT AUTO_INCREMENT PRIMARY KEY, sitemap_type VARCHAR(80), url_count INT DEFAULT 0, file_path VARCHAR(255), status VARCHAR(50), message TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_ad_placements (id INT AUTO_INCREMENT PRIMARY KEY, placement_key VARCHAR(120) UNIQUE, placement_name VARCHAR(160), page_type VARCHAR(80), ad_size VARCHAR(80), max_ads INT DEFAULT 1, rotate_ads TINYINT(1) DEFAULT 1, is_active TINYINT(1) DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_ad_packages (id INT AUTO_INCREMENT PRIMARY KEY, package_name VARCHAR(160), package_type VARCHAR(80), duration_days INT DEFAULT 7, price DECIMAL(12,2) DEFAULT 0, currency_code VARCHAR(8) DEFAULT 'SGD', placement_type VARCHAR(120), priority_level INT DEFAULT 0, is_active TINYINT(1) DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_sponsored_ads (id BIGINT AUTO_INCREMENT PRIMARY KEY, owner_user_id BIGINT NULL, company_id BIGINT NULL, related_type VARCHAR(80), related_id BIGINT NULL, placement_id INT NULL, title VARCHAR(255), image_path VARCHAR(255), target_url VARCHAR(500), country_id INT NULL, city_id BIGINT NULL, start_datetime DATETIME NULL, end_datetime DATETIME NULL, timezone VARCHAR(80) DEFAULT 'UTC', status ENUM('pending','active','scheduled','expired','paused','rejected','cancelled') DEFAULT 'pending', payment_status VARCHAR(60) DEFAULT 'unpaid', priority INT DEFAULT 0, impressions BIGINT DEFAULT 0, clicks BIGINT DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX(status), INDEX(end_datetime), INDEX(country_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_ad_impressions (id BIGINT AUTO_INCREMENT PRIMARY KEY, ad_id BIGINT, ip_address VARCHAR(80), page_url TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX(ad_id), INDEX(created_at)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_ad_clicks (id BIGINT AUTO_INCREMENT PRIMARY KEY, ad_id BIGINT, ip_address VARCHAR(80), page_url TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX(ad_id), INDEX(created_at)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_deal_categories (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(160), slug VARCHAR(180), icon VARCHAR(80), is_active TINYINT(1) DEFAULT 1, sort_order INT DEFAULT 0) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_deals (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT NULL,
  company_id BIGINT NULL,
  category_id INT NULL,
  country_id INT NULL,
  city_id BIGINT NULL,
  deal_type VARCHAR(60) NOT NULL DEFAULT 'normal',
  title VARCHAR(255) NOT NULL,
  slug VARCHAR(255) NULL,
  short_description VARCHAR(500) NULL,
  description MEDIUMTEXT NULL,
  image_path VARCHAR(500) NULL,
  original_price DECIMAL(12,2) DEFAULT 0,
  deal_price DECIMAL(12,2) DEFAULT 0,
  discount_percent DECIMAL(8,2) DEFAULT 0,
  price_note VARCHAR(255) NULL,
  currency_code VARCHAR(8) DEFAULT 'SGD',
  start_at DATETIME NULL,
  end_at DATETIME NULL,
  status VARCHAR(40) NOT NULL DEFAULT 'pending',
  is_featured TINYINT(1) NOT NULL DEFAULT 0,
  is_sponsored TINYINT(1) NOT NULL DEFAULT 0,
  featured_until DATETIME NULL,
  sponsored_until DATETIME NULL,
  contact_phone VARCHAR(80) NULL,
  contact_whatsapp VARCHAR(80) NULL,
  contact_email VARCHAR(190) NULL,
  terms TEXT NULL,
  quality_score INT NOT NULL DEFAULT 0,
  view_count INT NOT NULL DEFAULT 0,
  enquiry_count INT NOT NULL DEFAULT 0,
  whatsapp_click_count INT NOT NULL DEFAULT 0,
  call_click_count INT NOT NULL DEFAULT 0,
  email_click_count INT NOT NULL DEFAULT 0,
  share_count INT NOT NULL DEFAULT 0,
  qr_scan_count INT NOT NULL DEFAULT 0,
  reject_reason TEXT NULL,
  admin_note TEXT NULL,
  approved_by INT NULL,
  approved_at DATETIME NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  INDEX idx_deals_user_status(user_id,status),
  INDEX idx_deals_country_status(country_id,status),
  INDEX idx_deals_end_at(end_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_deal_purchases (id BIGINT AUTO_INCREMENT PRIMARY KEY, deal_id BIGINT, user_id BIGINT, quantity INT DEFAULT 1, total_amount DECIMAL(12,2), payment_status VARCHAR(60) DEFAULT 'pending', status VARCHAR(60) DEFAULT 'pending', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX(deal_id), INDEX(user_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_deal_vouchers (id BIGINT AUTO_INCREMENT PRIMARY KEY, purchase_id BIGINT, deal_id BIGINT, user_id BIGINT, voucher_code VARCHAR(80) UNIQUE, qr_text TEXT, status ENUM('purchased','active','redeemed','partially_redeemed','expired','cancelled','refunded','disputed') DEFAULT 'active', expires_at DATETIME NULL, redeemed_at DATETIME NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX(voucher_code), INDEX(user_id,status)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_deal_redemptions (id BIGINT AUTO_INCREMENT PRIMARY KEY, voucher_id BIGINT, company_id BIGINT NULL, branch_id BIGINT NULL, staff_id BIGINT NULL, redeem_note TEXT, ip_address VARCHAR(80), created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX(voucher_id), INDEX(company_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_deal_refunds (id BIGINT AUTO_INCREMENT PRIMARY KEY, purchase_id BIGINT, voucher_id BIGINT NULL, user_id BIGINT, reason TEXT, status VARCHAR(60) DEFAULT 'pending', amount DECIMAL(12,2) DEFAULT 0, admin_note TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_deal_payouts (id BIGINT AUTO_INCREMENT PRIMARY KEY, company_id BIGINT, period_start DATE, period_end DATE, gross_amount DECIMAL(12,2) DEFAULT 0, commission_amount DECIMAL(12,2) DEFAULT 0, refund_amount DECIMAL(12,2) DEFAULT 0, net_amount DECIMAL(12,2) DEFAULT 0, status VARCHAR(60) DEFAULT 'pending', created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_deal_commissions (id INT AUTO_INCREMENT PRIMARY KEY, rule_name VARCHAR(160), country_id INT NULL, category_id INT NULL, company_id BIGINT NULL, commission_type VARCHAR(40) DEFAULT 'percent', commission_value DECIMAL(12,2) DEFAULT 0, is_active TINYINT(1) DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_deal_reports (id BIGINT AUTO_INCREMENT PRIMARY KEY, deal_id BIGINT NULL, report_type VARCHAR(80), message TEXT, status VARCHAR(60) DEFAULT 'new', created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_deal_favorites (id BIGINT AUTO_INCREMENT PRIMARY KEY, user_id BIGINT, deal_id BIGINT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uniq_deal_fav(user_id,deal_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_deal_gifts (id BIGINT AUTO_INCREMENT PRIMARY KEY, purchase_id BIGINT, sender_user_id BIGINT, recipient_name VARCHAR(160), recipient_email VARCHAR(190), gift_message TEXT, send_at DATETIME NULL, status VARCHAR(60) DEFAULT 'pending', created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_promotions (id BIGINT AUTO_INCREMENT PRIMARY KEY, company_id BIGINT NULL, country_id INT NULL, city_id BIGINT NULL, title VARCHAR(255), slug VARCHAR(255), description TEXT, banner_image VARCHAR(255), promo_type VARCHAR(80), promo_code VARCHAR(80), discount_value DECIMAL(12,2) DEFAULT 0, start_at DATETIME NULL, end_at DATETIME NULL, status VARCHAR(60) DEFAULT 'pending', created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_coupons (id BIGINT AUTO_INCREMENT PRIMARY KEY, company_id BIGINT NULL, branch_id BIGINT NULL, country_id INT NULL, city_id BIGINT NULL, coupon_code VARCHAR(80) UNIQUE, title VARCHAR(255), discount_type VARCHAR(40), discount_value DECIMAL(12,2), min_spend DECIMAL(12,2) DEFAULT 0, max_discount DECIMAL(12,2) DEFAULT 0, usage_limit INT DEFAULT 0, per_user_limit INT DEFAULT 1, start_at DATETIME NULL, end_at DATETIME NULL, status VARCHAR(60) DEFAULT 'active', created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_coupon_redemptions (id BIGINT AUTO_INCREMENT PRIMARY KEY, coupon_id BIGINT, user_id BIGINT NULL, related_type VARCHAR(80), related_id BIGINT NULL, redeemed_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX(coupon_id), INDEX(user_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_quote_requests (id BIGINT AUTO_INCREMENT PRIMARY KEY, user_id BIGINT NULL, company_id BIGINT NULL, service_id BIGINT NULL, country_id INT NULL, city_id BIGINT NULL, title VARCHAR(255), description TEXT, urgency VARCHAR(80), status VARCHAR(60) DEFAULT 'new', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX(company_id,status), INDEX(user_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_quotes (id BIGINT AUTO_INCREMENT PRIMARY KEY, quote_request_id BIGINT NULL, company_id BIGINT NULL, user_id BIGINT NULL, quote_no VARCHAR(80), amount DECIMAL(12,2) DEFAULT 0, valid_until DATE NULL, notes TEXT, status VARCHAR(60) DEFAULT 'sent', created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_bookings (id BIGINT AUTO_INCREMENT PRIMARY KEY, user_id BIGINT NULL, company_id BIGINT NULL, branch_id BIGINT NULL, service_id BIGINT NULL, staff_id BIGINT NULL, booking_start DATETIME NULL, booking_end DATETIME NULL, timezone VARCHAR(80) DEFAULT 'UTC', status VARCHAR(60) DEFAULT 'pending', notes TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX(company_id,status), INDEX(user_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_bookings (fresh installer safe).
CALL mj_safe_add_column('mj_bookings', 'user_id', '`user_id` BIGINT NULL');
CALL mj_safe_add_column('mj_bookings', 'company_id', '`company_id` BIGINT NULL');
CALL mj_safe_add_column('mj_bookings', 'branch_id', '`branch_id` BIGINT NULL');
CALL mj_safe_add_column('mj_bookings', 'service_id', '`service_id` BIGINT NULL');
CALL mj_safe_add_column('mj_bookings', 'staff_id', '`staff_id` BIGINT NULL');
CALL mj_safe_add_column('mj_bookings', 'booking_start', '`booking_start` DATETIME NULL');
CALL mj_safe_add_column('mj_bookings', 'booking_end', '`booking_end` DATETIME NULL');
CALL mj_safe_add_column('mj_bookings', 'timezone', '`timezone` VARCHAR(80) DEFAULT ''UTC''');
CALL mj_safe_add_column('mj_bookings', 'status', '`status` VARCHAR(60) DEFAULT ''pending''');
CALL mj_safe_add_column('mj_bookings', 'notes', '`notes` TEXT');
CALL mj_safe_add_column('mj_bookings', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');

CREATE TABLE IF NOT EXISTS mj_company_staff (id BIGINT AUTO_INCREMENT PRIMARY KEY, company_id BIGINT, branch_id BIGINT NULL, user_id BIGINT NULL, name VARCHAR(160), email VARCHAR(190), role VARCHAR(80), permissions_json JSON NULL, is_active TINYINT(1) DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_company_leads (id BIGINT AUTO_INCREMENT PRIMARY KEY, company_id BIGINT, branch_id BIGINT NULL, user_id BIGINT NULL, source VARCHAR(80), title VARCHAR(255), status VARCHAR(60) DEFAULT 'new', priority VARCHAR(40) DEFAULT 'normal', lead_value DECIMAL(12,2) DEFAULT 0, followup_at DATETIME NULL, assigned_staff_id BIGINT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX(company_id,status)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_company_lead_notes (id BIGINT AUTO_INCREMENT PRIMARY KEY, lead_id BIGINT, staff_id BIGINT NULL, note TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_company_documents (id BIGINT AUTO_INCREMENT PRIMARY KEY, company_id BIGINT, document_type VARCHAR(80), file_path VARCHAR(255), status VARCHAR(60) DEFAULT 'pending', expires_at DATE NULL, admin_note TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_company_analytics (id BIGINT AUTO_INCREMENT PRIMARY KEY, company_id BIGINT, metric_date DATE, views INT DEFAULT 0, phone_clicks INT DEFAULT 0, whatsapp_clicks INT DEFAULT 0, website_clicks INT DEFAULT 0, leads INT DEFAULT 0, bookings INT DEFAULT 0, deals_sold INT DEFAULT 0, revenue DECIMAL(12,2) DEFAULT 0, UNIQUE KEY uniq_company_metric(company_id,metric_date)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_company_packages (id INT AUTO_INCREMENT PRIMARY KEY, package_name VARCHAR(160), price DECIMAL(12,2) DEFAULT 0, duration_days INT DEFAULT 30, branch_limit INT DEFAULT 1, staff_limit INT DEFAULT 1, service_limit INT DEFAULT 10, deal_limit INT DEFAULT 0, is_active TINYINT(1) DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_company_subscriptions (id BIGINT AUTO_INCREMENT PRIMARY KEY, company_id BIGINT, package_id INT, start_at DATETIME, end_at DATETIME, status VARCHAR(60) DEFAULT 'active', created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_company_claims (id BIGINT AUTO_INCREMENT PRIMARY KEY, company_id BIGINT NULL, seed_id BIGINT NULL, user_id BIGINT NULL, claimant_name VARCHAR(160), claimant_email VARCHAR(190), proof_file VARCHAR(255), status VARCHAR(60) DEFAULT 'pending', admin_note TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_company_claims (fresh installer safe).
CALL mj_safe_add_column('mj_company_claims', 'company_id', '`company_id` BIGINT NULL');
CALL mj_safe_add_column('mj_company_claims', 'seed_id', '`seed_id` BIGINT NULL');
CALL mj_safe_add_column('mj_company_claims', 'user_id', '`user_id` BIGINT NULL');
CALL mj_safe_add_column('mj_company_claims', 'claimant_name', '`claimant_name` VARCHAR(160)');
CALL mj_safe_add_column('mj_company_claims', 'claimant_email', '`claimant_email` VARCHAR(190)');
CALL mj_safe_add_column('mj_company_claims', 'proof_file', '`proof_file` VARCHAR(255)');
CALL mj_safe_add_column('mj_company_claims', 'status', '`status` VARCHAR(60) DEFAULT ''pending''');
CALL mj_safe_add_column('mj_company_claims', 'admin_note', '`admin_note` TEXT');
CALL mj_safe_add_column('mj_company_claims', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');

CREATE TABLE IF NOT EXISTS mj_wallet_transactions (id BIGINT AUTO_INCREMENT PRIMARY KEY, user_id BIGINT NULL, company_id BIGINT NULL, transaction_type VARCHAR(60), amount DECIMAL(12,2), currency_code VARCHAR(8) DEFAULT 'SGD', description TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_wallet_transactions (fresh installer safe).
CALL mj_safe_add_column('mj_wallet_transactions', 'user_id', '`user_id` BIGINT NULL');
CALL mj_safe_add_column('mj_wallet_transactions', 'company_id', '`company_id` BIGINT NULL');
CALL mj_safe_add_column('mj_wallet_transactions', 'transaction_type', '`transaction_type` VARCHAR(60)');
CALL mj_safe_add_column('mj_wallet_transactions', 'amount', '`amount` DECIMAL(12,2)');
CALL mj_safe_add_column('mj_wallet_transactions', 'currency_code', '`currency_code` VARCHAR(8) DEFAULT ''SGD''');
CALL mj_safe_add_column('mj_wallet_transactions', 'description', '`description` TEXT');
CALL mj_safe_add_column('mj_wallet_transactions', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');

CREATE TABLE IF NOT EXISTS mj_support_tickets (id BIGINT AUTO_INCREMENT PRIMARY KEY, user_id BIGINT NULL, company_id BIGINT NULL, subject VARCHAR(255), message TEXT, priority VARCHAR(40) DEFAULT 'normal', status VARCHAR(60) DEFAULT 'open', assigned_admin_id INT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_support_tickets (fresh installer safe).
CALL mj_safe_add_column('mj_support_tickets', 'user_id', '`user_id` BIGINT NULL');
CALL mj_safe_add_column('mj_support_tickets', 'company_id', '`company_id` BIGINT NULL');
CALL mj_safe_add_column('mj_support_tickets', 'subject', '`subject` VARCHAR(255)');
CALL mj_safe_add_column('mj_support_tickets', 'message', '`message` TEXT');
CALL mj_safe_add_column('mj_support_tickets', 'priority', '`priority` VARCHAR(40) DEFAULT ''normal''');
CALL mj_safe_add_column('mj_support_tickets', 'status', '`status` VARCHAR(60) DEFAULT ''open''');
CALL mj_safe_add_column('mj_support_tickets', 'assigned_admin_id', '`assigned_admin_id` INT NULL');
CALL mj_safe_add_column('mj_support_tickets', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');

CREATE TABLE IF NOT EXISTS mj_automation_rules (id INT AUTO_INCREMENT PRIMARY KEY, rule_name VARCHAR(160), event_type VARCHAR(100), condition_json JSON NULL, action_json JSON NULL, is_active TINYINT(1) DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT IGNORE INTO mj_social_providers(provider,is_enabled) VALUES('google',0),('facebook',0),('apple',0),('microsoft',0),('linkedin',0);
INSERT IGNORE INTO mj_ad_placements(placement_key,placement_name,page_type,ad_size,max_ads,rotate_ads,is_active) VALUES('homepage_top','Homepage Top Banner','home','970x250',3,1,1),('country_hero','Country Hero Banner','country','1080x300',2,1,1),('search_top','Search Result Top','search','responsive',5,1,1),('mobile_sticky','Mobile Sticky Bottom','mobile','320x100',1,0,1);
INSERT IGNORE INTO mj_ad_packages(package_name,package_type,duration_days,price,currency_code,placement_type,priority_level,is_active) VALUES('7 Days Featured Listing','listing',7,19.00,'SGD','featured_listing',10,1),('30 Days Featured Company','company',30,99.00,'SGD','featured_company',20,1),('7 Days Deal Boost','deal',7,29.00,'SGD','featured_deal',15,1);
-- MJ Marketplace v2.8.1 Account Center + CRM/Billing/Trust/Support
-- Safe additive migration. Run after v2.8.0 on existing installations.

CREATE TABLE IF NOT EXISTS mj_user_account_preferences (
  user_id BIGINT NOT NULL PRIMARY KEY,
  preferred_contact_method VARCHAR(40) DEFAULT 'whatsapp',
  profile_visibility VARCHAR(40) DEFAULT 'public',
  contact_visibility VARCHAR(40) DEFAULT 'login_required',
  language_preference VARCHAR(20) DEFAULT 'en',
  currency_code VARCHAR(8) DEFAULT 'SGD',
  timezone VARCHAR(80) DEFAULT 'Asia/Singapore',
  away_mode TINYINT(1) DEFAULT 0,
  away_message TEXT NULL,
  auto_reply_enabled TINYINT(1) DEFAULT 0,
  auto_reply_message TEXT NULL,
  notify_inapp TINYINT(1) DEFAULT 1,
  notify_email TINYINT(1) DEFAULT 1,
  notify_sms TINYINT(1) DEFAULT 0,
  notify_whatsapp TINYINT(1) DEFAULT 0,
  do_not_disturb TINYINT(1) DEFAULT 0,
  daily_digest TINYINT(1) DEFAULT 0,
  important_alerts_only TINYINT(1) DEFAULT 0,
  security_login_alert TINYINT(1) DEFAULT 1,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_saved_searches (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  title VARCHAR(190) NOT NULL,
  search_url VARCHAR(500) NULL,
  filters_json TEXT NULL,
  alert_enabled TINYINT(1) DEFAULT 1,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX(user_id),
  INDEX(alert_enabled)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_saved_searches (fresh installer safe).
CALL mj_safe_add_column('mj_saved_searches', 'user_id', '`user_id` BIGINT NOT NULL');
CALL mj_safe_add_column('mj_saved_searches', 'title', '`title` VARCHAR(190) NOT NULL');
CALL mj_safe_add_column('mj_saved_searches', 'search_url', '`search_url` VARCHAR(500) NULL');
CALL mj_safe_add_column('mj_saved_searches', 'filters_json', '`filters_json` TEXT NULL');
CALL mj_safe_add_column('mj_saved_searches', 'alert_enabled', '`alert_enabled` TINYINT(1) DEFAULT 1');
CALL mj_safe_add_column('mj_saved_searches', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');


CREATE TABLE IF NOT EXISTS mj_blocked_users (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  blocked_user_id BIGINT NULL,
  blocked_email VARCHAR(190) NULL,
  reason VARCHAR(255) NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  UNIQUE KEY uniq_block_user (user_id, blocked_user_id),
  INDEX(user_id),
  INDEX(blocked_email)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_user_activity_logs (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  action VARCHAR(120) NOT NULL,
  description TEXT NULL,
  ip_address VARCHAR(80) NULL,
  user_agent VARCHAR(255) NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX(user_id),
  INDEX(action),
  INDEX(created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_payment_requests (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  company_id BIGINT NULL,
  request_type VARCHAR(80) DEFAULT 'manual',
  reference_type VARCHAR(80) NULL,
  reference_id BIGINT NULL,
  title VARCHAR(190) NOT NULL,
  description TEXT NULL,
  amount DECIMAL(12,2) NOT NULL DEFAULT 0,
  tax_amount DECIMAL(12,2) NOT NULL DEFAULT 0,
  total_amount DECIMAL(12,2) NOT NULL DEFAULT 0,
  currency_code VARCHAR(8) DEFAULT 'SGD',
  status VARCHAR(40) DEFAULT 'pending',
  due_date DATE NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  INDEX(user_id),
  INDEX(status),
  INDEX(request_type)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_invoices (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  company_id BIGINT NULL,
  invoice_no VARCHAR(80) UNIQUE,
  invoice_type VARCHAR(40) DEFAULT 'invoice',
  title VARCHAR(190) NULL,
  amount DECIMAL(12,2) NOT NULL DEFAULT 0,
  tax_amount DECIMAL(12,2) NOT NULL DEFAULT 0,
  total_amount DECIMAL(12,2) NOT NULL DEFAULT 0,
  currency_code VARCHAR(8) DEFAULT 'SGD',
  status VARCHAR(40) DEFAULT 'draft',
  due_date DATE NULL,
  paid_at DATETIME NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX(user_id),
  INDEX(status),
  INDEX(invoice_no)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_refund_requests (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  invoice_id BIGINT NULL,
  payment_request_id BIGINT NULL,
  reason TEXT NULL,
  amount DECIMAL(12,2) DEFAULT 0,
  currency_code VARCHAR(8) DEFAULT 'SGD',
  status VARCHAR(40) DEFAULT 'pending',
  admin_note TEXT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX(user_id),
  INDEX(status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_verification_requests (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  company_id BIGINT NULL,
  verification_type VARCHAR(80) NOT NULL,
  document_path VARCHAR(255) NULL,
  note TEXT NULL,
  status VARCHAR(40) DEFAULT 'pending',
  admin_note TEXT NULL,
  expires_at DATE NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  reviewed_at DATETIME NULL,
  INDEX(user_id),
  INDEX(company_id),
  INDEX(status),
  INDEX(verification_type)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_support_ticket_messages (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  ticket_id BIGINT NOT NULL,
  sender_type VARCHAR(20) DEFAULT 'user',
  sender_user_id BIGINT NULL,
  sender_admin_id BIGINT NULL,
  message TEXT NOT NULL,
  attachment_path VARCHAR(255) NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX(ticket_id),
  INDEX(sender_type)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_quick_reply_templates (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  title VARCHAR(190) NOT NULL,
  message TEXT NOT NULL,
  is_active TINYINT(1) DEFAULT 1,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX(user_id),
  INDEX(is_active)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

INSERT INTO mj_settings(setting_key,setting_value) VALUES
('account_center_enabled','1'),
('crm_leads_enabled','1'),
('billing_payments_enabled','1'),
('trust_verification_enabled','1'),
('support_center_enabled','1'),
('account_center_profile_score_enabled','1'),
('crm_missed_lead_alert_hours','24'),
('crm_auto_reply_enabled','1'),
('support_ticket_upload_enabled','1'),
('billing_tax_enabled','0'),
('billing_tax_label','GST/VAT'),
('billing_default_tax_rate','0'),
('privacy_data_download_enabled','1'),
('privacy_delete_request_enabled','1')
ON DUPLICATE KEY UPDATE setting_value=setting_value;

INSERT INTO mj_site_modules(module_code,module_name,description,is_enabled) VALUES
('account_center','Account Center','Profile, company, wallet, security, privacy and user preferences',1),
('crm_leads','CRM / Leads','Lead inbox, follow-up reminders, customer timeline and CRM tools',1),
('billing_payments','Billing & Payments','Wallet, invoices, payment requests, receipts, refunds and tax settings',1),
('trust_verification','Trust & Verification','Verification, trust badges, safety, reports and device security',1),
('support_center','Help & Support','Tickets, FAQ, guides, attachments and admin replies',1),
('saved_search_alerts','Saved Search Alerts','Notify users when matching listings are added',1),
('seller_automation','Seller Automation','Auto reply, away mode, low performance alerts and renew reminders',1),
('company_team','Company Team','Staff users, roles, branches and lead assignment',1),
('marketing_tools','Marketing Tools','Share poster, QR poster, SEO preview and AI listing helper',1)
ON DUPLICATE KEY UPDATE module_name=VALUES(module_name), description=VALUES(description), is_enabled=VALUES(is_enabled);

-- v2.8.4 Admin User List Access Controls
CREATE TABLE IF NOT EXISTS mj_user_feature_access (
    user_id BIGINT NOT NULL PRIMARY KEY,
    account_center TINYINT(1) NOT NULL DEFAULT 1,
    crm_leads TINYINT(1) NOT NULL DEFAULT 1,
    billing_payments TINYINT(1) NOT NULL DEFAULT 1,
    wallet TINYINT(1) NOT NULL DEFAULT 1,
    payment_proof_upload TINYINT(1) NOT NULL DEFAULT 1,
    renew_listing TINYINT(1) NOT NULL DEFAULT 1,
    featured_listing TINYINT(1) NOT NULL DEFAULT 1,
    sponsored_listing TINYINT(1) NOT NULL DEFAULT 1,
    extra_listing_slots TINYINT(1) NOT NULL DEFAULT 1,
    extra_company_slots TINYINT(1) NOT NULL DEFAULT 1,
    trust_verification TINYINT(1) NOT NULL DEFAULT 1,
    support_center TINYINT(1) NOT NULL DEFAULT 1,
    admin_note TEXT NULL,
    updated_by BIGINT NULL,
    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX(updated_by)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;


-- Included from migration_v2.8.8_complete_cleanup_frontend_user_admin.sql
-- MJ Marketplace v2.8.8 Complete Frontend/User/Admin/Automation Cleanup
-- Safe migration: creates missing tables for country themes, company page builder, appointment booking,
-- saved searches, admin notes and system run logs. Existing data is preserved.

CREATE TABLE IF NOT EXISTS mj_company_page_settings (
  company_id BIGINT NOT NULL PRIMARY KEY,
  layout_style VARCHAR(80) DEFAULT 'modern_card',
  theme_primary VARCHAR(32) NULL,
  theme_secondary VARCHAR(32) NULL,
  theme_accent VARCHAR(32) NULL,
  cover_image VARCHAR(255) NULL,
  video_url VARCHAR(255) NULL,
  show_reviews TINYINT(1) NOT NULL DEFAULT 1,
  show_booking TINYINT(1) NOT NULL DEFAULT 0,
  seo_title VARCHAR(255) NULL,
  seo_description TEXT NULL,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_company_page_sections (
  id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
  company_id BIGINT NOT NULL,
  section_key VARCHAR(80) NOT NULL,
  section_title VARCHAR(255) NULL,
  section_content MEDIUMTEXT NULL,
  is_enabled TINYINT(1) NOT NULL DEFAULT 1,
  sort_order INT NOT NULL DEFAULT 0,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  INDEX(company_id), INDEX(section_key)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_company_booking_settings (
  company_id BIGINT NOT NULL PRIMARY KEY,
  is_enabled TINYINT(1) NOT NULL DEFAULT 0,
  booking_type VARCHAR(80) DEFAULT 'appointment',
  slot_duration INT NOT NULL DEFAULT 30,
  buffer_minutes INT NOT NULL DEFAULT 0,
  max_bookings_per_day INT NOT NULL DEFAULT 20,
  advance_days INT NOT NULL DEFAULT 30,
  same_day_booking TINYINT(1) NOT NULL DEFAULT 1,
  manual_approval TINYINT(1) NOT NULL DEFAULT 1,
  allow_reschedule TINYINT(1) NOT NULL DEFAULT 1,
  allow_cancel TINYINT(1) NOT NULL DEFAULT 1,
  business_hours_json TEXT NULL,
  closed_dates_json TEXT NULL,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_appointments (
  id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT NULL,
  seller_user_id BIGINT NULL,
  company_id BIGINT NULL,
  listing_id BIGINT NULL,
  service_id BIGINT NULL,
  branch_id BIGINT NULL,
  customer_name VARCHAR(190) NULL,
  customer_email VARCHAR(190) NULL,
  customer_phone VARCHAR(80) NULL,
  appointment_datetime DATETIME NULL,
  duration_minutes INT NOT NULL DEFAULT 30,
  status VARCHAR(50) NOT NULL DEFAULT 'pending',
  note TEXT NULL,
  admin_note TEXT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  INDEX(user_id), INDEX(seller_user_id), INDEX(company_id), INDEX(status), INDEX(appointment_datetime)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_admin_notes (
  id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
  admin_id BIGINT NULL,
  related_type VARCHAR(80) NOT NULL,
  related_id BIGINT NOT NULL,
  note TEXT NOT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX(related_type,related_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_saved_searches (
  id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  title VARCHAR(190) NULL,
  query_json TEXT NULL,
  alert_enabled TINYINT(1) NOT NULL DEFAULT 1,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX(user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_saved_searches (fresh installer safe).
CALL mj_safe_add_column('mj_saved_searches', 'user_id', '`user_id` BIGINT NOT NULL');
CALL mj_safe_add_column('mj_saved_searches', 'title', '`title` VARCHAR(190) NULL');
CALL mj_safe_add_column('mj_saved_searches', 'query_json', '`query_json` TEXT NULL');
CALL mj_safe_add_column('mj_saved_searches', 'alert_enabled', '`alert_enabled` TINYINT(1) NOT NULL DEFAULT 1');
CALL mj_safe_add_column('mj_saved_searches', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');


CREATE TABLE IF NOT EXISTS mj_system_run_logs (
  id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
  task_key VARCHAR(120) NOT NULL,
  status VARCHAR(50) NOT NULL DEFAULT 'success',
  message TEXT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX(task_key)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- Add these columns to mj_countries if your MySQL supports ALTER ADD COLUMN checks manually.
-- If column already exists, skip that ALTER line.
-- ALTER TABLE mj_countries ADD COLUMN theme_primary VARCHAR(32) NULL;
-- ALTER TABLE mj_countries ADD COLUMN theme_secondary VARCHAR(32) NULL;
-- ALTER TABLE mj_countries ADD COLUMN theme_accent VARCHAR(32) NULL;
-- ALTER TABLE mj_countries ADD COLUMN theme_text VARCHAR(32) NULL;
-- ALTER TABLE mj_countries ADD COLUMN theme_gradient VARCHAR(255) NULL;
-- ALTER TABLE mj_countries ADD COLUMN use_flag_theme TINYINT(1) NOT NULL DEFAULT 1;

INSERT INTO mj_site_modules(module_code,module_name,description,is_enabled) VALUES
('selling_center','Selling Center','Unified seller dashboard, listings, companies, CRM, booking and upgrades',1),
('buying_center','Buying Center','Unified buyer dashboard, saved items, offers, quotes, appointments and reports',1),
('appointment_booking','Appointment Booking','Company/listing appointment booking and appointment center',1),
('company_page_builder','Company Page Builder','Flexible company detail page colors, sections, SEO and booking button',1),
('saved_search_alerts','Saved Searches & Price Alerts','Saved searches, price alerts and matching listing alerts',1),
('qr_share_tools','QR / Share Poster Tools','QR codes and share poster tools for listing/company marketing',1),
('reviews_ratings','Reviews & Ratings','Reviews, ratings, moderation and trust badges',1),
('import_export','Import / Export','CSV import/export controls for admin and allowed users',1)
ON DUPLICATE KEY UPDATE module_name=VALUES(module_name), description=VALUES(description), is_enabled=VALUES(is_enabled);

-- v2.9.1 Bulk Payment Cart + Invoice Checkout tables
CREATE TABLE IF NOT EXISTS mj_payment_batches (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  batch_no VARCHAR(80) NULL UNIQUE,
  invoice_id BIGINT NULL,
  user_id BIGINT NOT NULL,
  country_id BIGINT NULL,
  currency_code VARCHAR(10) NOT NULL DEFAULT 'SGD',
  subtotal_amount DECIMAL(12,2) NOT NULL DEFAULT 0,
  tax_amount DECIMAL(12,2) NOT NULL DEFAULT 0,
  total_amount DECIMAL(12,2) NOT NULL DEFAULT 0,
  payment_method VARCHAR(80) NULL,
  reference_no VARCHAR(190) NULL,
  proof_path VARCHAR(500) NULL,
  original_name VARCHAR(255) NULL,
  mime_type VARCHAR(120) NULL,
  file_size BIGINT NULL,
  note TEXT NULL,
  status VARCHAR(50) NOT NULL DEFAULT 'pending_review',
  admin_note TEXT NULL,
  reviewed_by BIGINT NULL,
  reviewed_at DATETIME NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  INDEX(user_id), INDEX(invoice_id), INDEX(status), INDEX(created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_payment_batch_items (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  batch_id BIGINT NOT NULL,
  user_id BIGINT NOT NULL,
  item_type VARCHAR(80) NOT NULL,
  item_id BIGINT NOT NULL DEFAULT 0,
  item_label VARCHAR(190) NULL,
  item_description TEXT NULL,
  listing_id BIGINT NULL,
  company_id BIGINT NULL,
  quantity INT NOT NULL DEFAULT 1,
  unit_amount DECIMAL(12,2) NOT NULL DEFAULT 0,
  total_amount DECIMAL(12,2) NOT NULL DEFAULT 0,
  currency_code VARCHAR(10) NOT NULL DEFAULT 'SGD',
  status VARCHAR(50) NOT NULL DEFAULT 'pending_review',
  activation_status VARCHAR(50) NOT NULL DEFAULT 'not_activated',
  admin_note TEXT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  INDEX(batch_id), INDEX(user_id), INDEX(item_type,item_id), INDEX(status), INDEX(activation_status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;


-- v2.9.16 Deals display/enquiry and SMTP email tables
CREATE TABLE IF NOT EXISTS mj_deal_enquiries (id BIGINT AUTO_INCREMENT PRIMARY KEY, deal_id BIGINT NOT NULL, seller_user_id BIGINT NULL, company_id BIGINT NULL, customer_user_id BIGINT NULL, name VARCHAR(160) NOT NULL, email VARCHAR(190) NULL, phone VARCHAR(80) NULL, preferred_datetime VARCHAR(120) NULL, message TEXT NULL, status VARCHAR(40) NOT NULL DEFAULT 'new', source VARCHAR(60) NOT NULL DEFAULT 'deal_page', admin_note TEXT NULL, ip_address VARCHAR(80) NULL, user_agent TEXT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NULL, INDEX idx_deal_status(deal_id,status), INDEX idx_seller_status(seller_user_id,status), INDEX idx_company(company_id), INDEX idx_created(created_at)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;


CREATE TABLE IF NOT EXISTS mj_deal_import_logs (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  admin_id INT NULL,
  filename VARCHAR(255) NULL,
  total_rows INT NOT NULL DEFAULT 0,
  imported_rows INT NOT NULL DEFAULT 0,
  updated_rows INT NOT NULL DEFAULT 0,
  skipped_rows INT NOT NULL DEFAULT 0,
  failed_rows INT NOT NULL DEFAULT 0,
  default_status VARCHAR(40) NULL,
  duplicate_mode VARCHAR(40) NULL,
  error_json LONGTEXT NULL,
  ip_address VARCHAR(80) NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_created(created_at),
  INDEX idx_admin_created(admin_id, created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_deal_events (id BIGINT AUTO_INCREMENT PRIMARY KEY, deal_id BIGINT NOT NULL, user_id BIGINT NULL, event_type VARCHAR(80) NOT NULL, event_value VARCHAR(255) NULL, ip_address VARCHAR(80) NULL, user_agent TEXT NULL, session_key VARCHAR(191) NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_deal_type(deal_id,event_type), INDEX idx_created(created_at), INDEX idx_session(session_key)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_smtp_accounts (id INT AUTO_INCREMENT PRIMARY KEY, account_name VARCHAR(160) NOT NULL, host VARCHAR(190) NOT NULL, port INT NOT NULL DEFAULT 587, encryption VARCHAR(20) NOT NULL DEFAULT 'tls', username VARCHAR(190) NULL, password TEXT NULL, from_email VARCHAR(190) NOT NULL, from_name VARCHAR(190) NULL, reply_to_email VARCHAR(190) NULL, is_active TINYINT(1) NOT NULL DEFAULT 1, priority_order INT NOT NULL DEFAULT 100, daily_limit INT NOT NULL DEFAULT 0, sent_today INT NOT NULL DEFAULT 0, last_sent_at DATETIME NULL, last_error TEXT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_active_priority(is_active, priority_order), INDEX idx_from_email(from_email)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_email_send_log (id BIGINT AUTO_INCREMENT PRIMARY KEY, smtp_account_id INT NULL, to_email VARCHAR(190) NOT NULL, subject VARCHAR(255) NOT NULL, template_key VARCHAR(120) NULL, status VARCHAR(40) NOT NULL DEFAULT 'pending', error_message TEXT NULL, attempts INT NOT NULL DEFAULT 0, sent_at DATETIME NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_status_created(status, created_at), INDEX idx_to_email(to_email), INDEX idx_smtp_account(smtp_account_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- v2.9.17 update review workflow tables
CREATE TABLE IF NOT EXISTS mj_company_revisions (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  company_id BIGINT NOT NULL,
  user_id BIGINT NULL,
  old_data_json LONGTEXT NULL,
  new_data_json LONGTEXT NULL,
  old_extra_json LONGTEXT NULL,
  new_extra_json LONGTEXT NULL,
  status VARCHAR(40) NOT NULL DEFAULT 'pending',
  admin_id BIGINT NULL,
  admin_note TEXT NULL,
  reject_reason TEXT NULL,
  submitted_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  reviewed_at DATETIME NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_company_status(company_id,status),
  INDEX idx_user_status(user_id,status),
  INDEX idx_submitted(submitted_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_deal_revisions (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  deal_id BIGINT NOT NULL,
  user_id BIGINT NULL,
  old_data_json LONGTEXT NULL,
  new_data_json LONGTEXT NULL,
  old_extra_json LONGTEXT NULL,
  new_extra_json LONGTEXT NULL,
  status VARCHAR(40) NOT NULL DEFAULT 'pending',
  admin_id BIGINT NULL,
  admin_note TEXT NULL,
  reject_reason TEXT NULL,
  submitted_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  reviewed_at DATETIME NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_deal_status(deal_id,status),
  INDEX idx_user_status(user_id,status),
  INDEX idx_submitted(submitted_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- v2.9.19 User/Seller-to-Admin Service Cart
CREATE TABLE IF NOT EXISTS mj_admin_charge_cart (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  item_type VARCHAR(80) NOT NULL,
  target_type VARCHAR(80) NULL,
  target_id BIGINT NULL,
  title VARCHAR(190) NOT NULL,
  description TEXT NULL,
  quantity INT NOT NULL DEFAULT 1,
  unit_amount DECIMAL(12,2) NOT NULL DEFAULT 0,
  total_amount DECIMAL(12,2) NOT NULL DEFAULT 0,
  currency_code VARCHAR(10) NOT NULL DEFAULT 'SGD',
  duration_days INT NULL,
  status VARCHAR(40) NOT NULL DEFAULT 'cart',
  metadata_json MEDIUMTEXT NULL,
  batch_id BIGINT NULL,
  invoice_id BIGINT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  INDEX idx_user_status(user_id,status),
  INDEX idx_type(item_type),
  INDEX idx_target(target_type,target_id),
  INDEX idx_batch(batch_id),
  INDEX idx_created(created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- v3.0.2 Unified user/admin leads inbox notes
CREATE TABLE IF NOT EXISTS mj_lead_notes (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  customer_id BIGINT NOT NULL,
  actor_user_id BIGINT NULL,
  actor_admin_id BIGINT NULL,
  note TEXT NULL,
  status_from VARCHAR(40) NULL,
  status_to VARCHAR(40) NULL,
  followup_at DATETIME NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX(customer_id), INDEX(actor_user_id), INDEX(actor_admin_id), INDEX(created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;



-- ============================================================
-- Source: install/migration_global_pro.sql
-- ============================================================

-- MJ Marketplace Global Pro migration placeholder
-- Use this when upgrading older MJ Marketplace installations.
-- Always backup database and uploads before running migrations.
-- Removed MySQL client-only SOURCE command for phpMyAdmin/PDO installer compatibility: SOURCE schema.sql;



-- ============================================================
-- Source: install/migration_advanced_admin_user_area.sql
-- ============================================================

-- MJ Marketplace Global Pro - Advanced admin, user account and area-priority upgrade
CREATE TABLE IF NOT EXISTS mj_visitor_locations (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  country_id INT NULL,
  state_id BIGINT NULL,
  city_id BIGINT NULL,
  country_iso2 CHAR(2) NULL,
  country_name VARCHAR(160) NULL,
  state_name VARCHAR(180) NULL,
  city_name VARCHAR(180) NULL,
  timezone VARCHAR(80) NULL,
  latitude DECIMAL(12,8) NULL,
  longitude DECIMAL(12,8) NULL,
  ip_address VARCHAR(80) NULL,
  user_agent VARCHAR(255) NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_visitor_country(country_id),
  INDEX idx_visitor_city(city_id),
  INDEX idx_visitor_created(created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_visitor_locations (fresh installer safe).
CALL mj_safe_add_column('mj_visitor_locations', 'country_id', '`country_id` INT NULL');
CALL mj_safe_add_column('mj_visitor_locations', 'state_id', '`state_id` BIGINT NULL');
CALL mj_safe_add_column('mj_visitor_locations', 'city_id', '`city_id` BIGINT NULL');
CALL mj_safe_add_column('mj_visitor_locations', 'country_iso2', '`country_iso2` CHAR(2) NULL');
CALL mj_safe_add_column('mj_visitor_locations', 'country_name', '`country_name` VARCHAR(160) NULL');
CALL mj_safe_add_column('mj_visitor_locations', 'state_name', '`state_name` VARCHAR(180) NULL');
CALL mj_safe_add_column('mj_visitor_locations', 'city_name', '`city_name` VARCHAR(180) NULL');
CALL mj_safe_add_column('mj_visitor_locations', 'timezone', '`timezone` VARCHAR(80) NULL');
CALL mj_safe_add_column('mj_visitor_locations', 'latitude', '`latitude` DECIMAL(12,8) NULL');
CALL mj_safe_add_column('mj_visitor_locations', 'longitude', '`longitude` DECIMAL(12,8) NULL');
CALL mj_safe_add_column('mj_visitor_locations', 'ip_address', '`ip_address` VARCHAR(80) NULL');
CALL mj_safe_add_column('mj_visitor_locations', 'user_agent', '`user_agent` VARCHAR(255) NULL');
CALL mj_safe_add_column('mj_visitor_locations', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');


CREATE TABLE IF NOT EXISTS mj_saved_listings (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  listing_id BIGINT NOT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  UNIQUE KEY uniq_saved_listing(user_id,listing_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_saved_listings (fresh installer safe).
CALL mj_safe_add_column('mj_saved_listings', 'user_id', '`user_id` BIGINT NOT NULL');
CALL mj_safe_add_column('mj_saved_listings', 'listing_id', '`listing_id` BIGINT NOT NULL');
CALL mj_safe_add_column('mj_saved_listings', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');


CREATE TABLE IF NOT EXISTS mj_saved_companies (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  company_id BIGINT NOT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  UNIQUE KEY uniq_saved_company(user_id,company_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_saved_companies (fresh installer safe).
CALL mj_safe_add_column('mj_saved_companies', 'user_id', '`user_id` BIGINT NOT NULL');
CALL mj_safe_add_column('mj_saved_companies', 'company_id', '`company_id` BIGINT NOT NULL');
CALL mj_safe_add_column('mj_saved_companies', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');


CREATE TABLE IF NOT EXISTS mj_saved_searches (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  title VARCHAR(180) NOT NULL,
  search_url VARCHAR(255) NOT NULL,
  alert_enabled TINYINT(1) DEFAULT 0,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_saved_searches (fresh installer safe).
CALL mj_safe_add_column('mj_saved_searches', 'user_id', '`user_id` BIGINT NOT NULL');
CALL mj_safe_add_column('mj_saved_searches', 'title', '`title` VARCHAR(180) NOT NULL');
CALL mj_safe_add_column('mj_saved_searches', 'search_url', '`search_url` VARCHAR(255) NOT NULL');
CALL mj_safe_add_column('mj_saved_searches', 'alert_enabled', '`alert_enabled` TINYINT(1) DEFAULT 0');
CALL mj_safe_add_column('mj_saved_searches', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');


CREATE TABLE IF NOT EXISTS mj_user_devices (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  ip_address VARCHAR(80) NULL,
  user_agent VARCHAR(255) NULL,
  last_seen_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_device_user(user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_user_devices (fresh installer safe).
CALL mj_safe_add_column('mj_user_devices', 'user_id', '`user_id` BIGINT NOT NULL');
CALL mj_safe_add_column('mj_user_devices', 'ip_address', '`ip_address` VARCHAR(80) NULL');
CALL mj_safe_add_column('mj_user_devices', 'user_agent', '`user_agent` VARCHAR(255) NULL');
CALL mj_safe_add_column('mj_user_devices', 'last_seen_at', '`last_seen_at` DATETIME DEFAULT CURRENT_TIMESTAMP');
CALL mj_safe_add_column('mj_user_devices', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');


CREATE TABLE IF NOT EXISTS mj_user_privacy_requests (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  request_type VARCHAR(80) NOT NULL,
  status VARCHAR(40) DEFAULT 'pending',
  notes TEXT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_user_privacy_requests (fresh installer safe).
CALL mj_safe_add_column('mj_user_privacy_requests', 'user_id', '`user_id` BIGINT NOT NULL');
CALL mj_safe_add_column('mj_user_privacy_requests', 'request_type', '`request_type` VARCHAR(80) NOT NULL');
CALL mj_safe_add_column('mj_user_privacy_requests', 'status', '`status` VARCHAR(40) DEFAULT ''pending''');
CALL mj_safe_add_column('mj_user_privacy_requests', 'notes', '`notes` TEXT NULL');
CALL mj_safe_add_column('mj_user_privacy_requests', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');


CREATE TABLE IF NOT EXISTS mj_search_logs (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT NULL,
  country_id INT NULL,
  keyword VARCHAR(190) NULL,
  result_type VARCHAR(40) NULL,
  ip_address VARCHAR(80) NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_search_country(country_id),
  INDEX idx_search_keyword(keyword)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_search_logs (fresh installer safe).
CALL mj_safe_add_column('mj_search_logs', 'user_id', '`user_id` BIGINT NULL');
CALL mj_safe_add_column('mj_search_logs', 'country_id', '`country_id` INT NULL');
CALL mj_safe_add_column('mj_search_logs', 'keyword', '`keyword` VARCHAR(190) NULL');
CALL mj_safe_add_column('mj_search_logs', 'result_type', '`result_type` VARCHAR(40) NULL');
CALL mj_safe_add_column('mj_search_logs', 'ip_address', '`ip_address` VARCHAR(80) NULL');
CALL mj_safe_add_column('mj_search_logs', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');



INSERT INTO mj_settings(setting_key,setting_value) VALUES
('auto_area_priority','1'),
('allow_ip_area_detection','1'),
('maintenance_mode','0'),
('super_admin_login_as_user','1'),
('default_nearby_sort','city_state_country_featured')
ON DUPLICATE KEY UPDATE setting_value=VALUES(setting_value);



-- ============================================================
-- Source: install/migration_ultimate_all_modules.sql
-- ============================================================



/* Migration: MJ Marketplace Ultimate Modules */
CREATE TABLE IF NOT EXISTS mj_admin_alerts (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  alert_key VARCHAR(191) NOT NULL UNIQUE,
  alert_type VARCHAR(80) NOT NULL,
  module_name VARCHAR(80) DEFAULT NULL,
  priority ENUM('low','normal','high','urgent','critical') DEFAULT 'normal',
  title VARCHAR(255) NOT NULL,
  message TEXT NULL,
  action_url VARCHAR(500) DEFAULT NULL,
  action_label VARCHAR(100) DEFAULT NULL,
  country_id INT DEFAULT NULL,
  city_id BIGINT DEFAULT NULL,
  related_type VARCHAR(80) DEFAULT NULL,
  related_id BIGINT DEFAULT NULL,
  target_role VARCHAR(80) DEFAULT NULL,
  is_read TINYINT(1) DEFAULT 0,
  read_at DATETIME DEFAULT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  expires_at DATETIME DEFAULT NULL,
  INDEX(alert_type), INDEX(priority), INDEX(is_read), INDEX(created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_admin_alerts (fresh installer safe).
CALL mj_safe_add_column('mj_admin_alerts', 'alert_key', '`alert_key` VARCHAR(191) NOT NULL UNIQUE');
CALL mj_safe_add_column('mj_admin_alerts', 'alert_type', '`alert_type` VARCHAR(80) NOT NULL');
CALL mj_safe_add_column('mj_admin_alerts', 'module_name', '`module_name` VARCHAR(80) DEFAULT NULL');
CALL mj_safe_add_column('mj_admin_alerts', 'priority', '`priority` ENUM(''low'',''normal'',''high'',''urgent'',''critical'') DEFAULT ''normal''');
CALL mj_safe_add_column('mj_admin_alerts', 'title', '`title` VARCHAR(255) NOT NULL');
CALL mj_safe_add_column('mj_admin_alerts', 'message', '`message` TEXT NULL');
CALL mj_safe_add_column('mj_admin_alerts', 'action_url', '`action_url` VARCHAR(500) DEFAULT NULL');
CALL mj_safe_add_column('mj_admin_alerts', 'action_label', '`action_label` VARCHAR(100) DEFAULT NULL');
CALL mj_safe_add_column('mj_admin_alerts', 'country_id', '`country_id` INT DEFAULT NULL');
CALL mj_safe_add_column('mj_admin_alerts', 'city_id', '`city_id` BIGINT DEFAULT NULL');
CALL mj_safe_add_column('mj_admin_alerts', 'related_type', '`related_type` VARCHAR(80) DEFAULT NULL');
CALL mj_safe_add_column('mj_admin_alerts', 'related_id', '`related_id` BIGINT DEFAULT NULL');
CALL mj_safe_add_column('mj_admin_alerts', 'target_role', '`target_role` VARCHAR(80) DEFAULT NULL');
CALL mj_safe_add_column('mj_admin_alerts', 'is_read', '`is_read` TINYINT(1) DEFAULT 0');
CALL mj_safe_add_column('mj_admin_alerts', 'read_at', '`read_at` DATETIME DEFAULT NULL');
CALL mj_safe_add_column('mj_admin_alerts', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');
CALL mj_safe_add_column('mj_admin_alerts', 'expires_at', '`expires_at` DATETIME DEFAULT NULL');

CREATE TABLE IF NOT EXISTS mj_error_logs (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  error_hash VARCHAR(191) DEFAULT NULL,
  error_type VARCHAR(80) NOT NULL,
  error_level VARCHAR(80) DEFAULT NULL,
  error_message TEXT NULL,
  error_file VARCHAR(500) DEFAULT NULL,
  error_line INT DEFAULT NULL,
  request_url TEXT NULL,
  request_method VARCHAR(20) DEFAULT NULL,
  referrer_url TEXT NULL,
  user_id BIGINT DEFAULT NULL,
  admin_id INT DEFAULT NULL,
  ip_address VARCHAR(80) DEFAULT NULL,
  user_agent TEXT NULL,
  post_data LONGTEXT NULL,
  get_data LONGTEXT NULL,
  session_data LONGTEXT NULL,
  stack_trace LONGTEXT NULL,
  status ENUM('new','reviewing','fixed','ignored','reopened') DEFAULT 'new',
  priority ENUM('low','normal','high','urgent','critical') DEFAULT 'high',
  occurrence_count INT DEFAULT 1,
  first_seen_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  last_seen_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  fixed_at DATETIME DEFAULT NULL,
  fixed_by INT DEFAULT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX(error_hash), INDEX(error_type), INDEX(status), INDEX(priority), INDEX(created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_error_logs (fresh installer safe).
CALL mj_safe_add_column('mj_error_logs', 'error_hash', '`error_hash` VARCHAR(191) DEFAULT NULL');
CALL mj_safe_add_column('mj_error_logs', 'error_type', '`error_type` VARCHAR(80) NOT NULL');
CALL mj_safe_add_column('mj_error_logs', 'error_level', '`error_level` VARCHAR(80) DEFAULT NULL');
CALL mj_safe_add_column('mj_error_logs', 'error_message', '`error_message` TEXT NULL');
CALL mj_safe_add_column('mj_error_logs', 'error_file', '`error_file` VARCHAR(500) DEFAULT NULL');
CALL mj_safe_add_column('mj_error_logs', 'error_line', '`error_line` INT DEFAULT NULL');
CALL mj_safe_add_column('mj_error_logs', 'request_url', '`request_url` TEXT NULL');
CALL mj_safe_add_column('mj_error_logs', 'request_method', '`request_method` VARCHAR(20) DEFAULT NULL');
CALL mj_safe_add_column('mj_error_logs', 'referrer_url', '`referrer_url` TEXT NULL');
CALL mj_safe_add_column('mj_error_logs', 'user_id', '`user_id` BIGINT DEFAULT NULL');
CALL mj_safe_add_column('mj_error_logs', 'admin_id', '`admin_id` INT DEFAULT NULL');
CALL mj_safe_add_column('mj_error_logs', 'ip_address', '`ip_address` VARCHAR(80) DEFAULT NULL');
CALL mj_safe_add_column('mj_error_logs', 'user_agent', '`user_agent` TEXT NULL');
CALL mj_safe_add_column('mj_error_logs', 'post_data', '`post_data` LONGTEXT NULL');
CALL mj_safe_add_column('mj_error_logs', 'get_data', '`get_data` LONGTEXT NULL');
CALL mj_safe_add_column('mj_error_logs', 'session_data', '`session_data` LONGTEXT NULL');
CALL mj_safe_add_column('mj_error_logs', 'stack_trace', '`stack_trace` LONGTEXT NULL');
CALL mj_safe_add_column('mj_error_logs', 'status', '`status` ENUM(''new'',''reviewing'',''fixed'',''ignored'',''reopened'') DEFAULT ''new''');
CALL mj_safe_add_column('mj_error_logs', 'priority', '`priority` ENUM(''low'',''normal'',''high'',''urgent'',''critical'') DEFAULT ''high''');
CALL mj_safe_add_column('mj_error_logs', 'occurrence_count', '`occurrence_count` INT DEFAULT 1');
CALL mj_safe_add_column('mj_error_logs', 'first_seen_at', '`first_seen_at` DATETIME DEFAULT CURRENT_TIMESTAMP');
CALL mj_safe_add_column('mj_error_logs', 'last_seen_at', '`last_seen_at` DATETIME DEFAULT CURRENT_TIMESTAMP');
CALL mj_safe_add_column('mj_error_logs', 'fixed_at', '`fixed_at` DATETIME DEFAULT NULL');
CALL mj_safe_add_column('mj_error_logs', 'fixed_by', '`fixed_by` INT DEFAULT NULL');
CALL mj_safe_add_column('mj_error_logs', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');

CREATE TABLE IF NOT EXISTS mj_admin_alert_rules (id INT AUTO_INCREMENT PRIMARY KEY, rule_name VARCHAR(160), alert_type VARCHAR(80), target_role VARCHAR(80), country_id INT NULL, is_enabled TINYINT(1) DEFAULT 1, settings_json JSON NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_admin_alert_rules (fresh installer safe).
CALL mj_safe_add_column('mj_admin_alert_rules', 'rule_name', '`rule_name` VARCHAR(160)');
CALL mj_safe_add_column('mj_admin_alert_rules', 'alert_type', '`alert_type` VARCHAR(80)');
CALL mj_safe_add_column('mj_admin_alert_rules', 'target_role', '`target_role` VARCHAR(80)');
CALL mj_safe_add_column('mj_admin_alert_rules', 'country_id', '`country_id` INT NULL');
CALL mj_safe_add_column('mj_admin_alert_rules', 'is_enabled', '`is_enabled` TINYINT(1) DEFAULT 1');
CALL mj_safe_add_column('mj_admin_alert_rules', 'settings_json', '`settings_json` JSON NULL');
CALL mj_safe_add_column('mj_admin_alert_rules', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');

CREATE TABLE IF NOT EXISTS mj_notification_templates (id INT AUTO_INCREMENT PRIMARY KEY, template_key VARCHAR(120) UNIQUE, channel VARCHAR(40) DEFAULT 'in_app', title VARCHAR(255), body TEXT, variables_json JSON NULL, is_active TINYINT(1) DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_notification_templates (fresh installer safe).
CALL mj_safe_add_column('mj_notification_templates', 'template_key', '`template_key` VARCHAR(120) UNIQUE');
CALL mj_safe_add_column('mj_notification_templates', 'channel', '`channel` VARCHAR(40) DEFAULT ''in_app''');
CALL mj_safe_add_column('mj_notification_templates', 'title', '`title` VARCHAR(255)');
CALL mj_safe_add_column('mj_notification_templates', 'body', '`body` TEXT');
CALL mj_safe_add_column('mj_notification_templates', 'variables_json', '`variables_json` JSON NULL');
CALL mj_safe_add_column('mj_notification_templates', 'is_active', '`is_active` TINYINT(1) DEFAULT 1');
CALL mj_safe_add_column('mj_notification_templates', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');

CREATE TABLE IF NOT EXISTS mj_notification_rules (id INT AUTO_INCREMENT PRIMARY KEY, rule_key VARCHAR(120) UNIQUE, rule_name VARCHAR(160), event_type VARCHAR(100), channel VARCHAR(40), timing_rule VARCHAR(100), is_enabled TINYINT(1) DEFAULT 1, settings_json JSON NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_notification_rules (fresh installer safe).
CALL mj_safe_add_column('mj_notification_rules', 'rule_key', '`rule_key` VARCHAR(120) UNIQUE');
CALL mj_safe_add_column('mj_notification_rules', 'rule_name', '`rule_name` VARCHAR(160)');
CALL mj_safe_add_column('mj_notification_rules', 'event_type', '`event_type` VARCHAR(100)');
CALL mj_safe_add_column('mj_notification_rules', 'channel', '`channel` VARCHAR(40)');
CALL mj_safe_add_column('mj_notification_rules', 'timing_rule', '`timing_rule` VARCHAR(100)');
CALL mj_safe_add_column('mj_notification_rules', 'is_enabled', '`is_enabled` TINYINT(1) DEFAULT 1');
CALL mj_safe_add_column('mj_notification_rules', 'settings_json', '`settings_json` JSON NULL');
CALL mj_safe_add_column('mj_notification_rules', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');

CREATE TABLE IF NOT EXISTS mj_notifications (id BIGINT AUTO_INCREMENT PRIMARY KEY, user_id BIGINT NULL, company_id BIGINT NULL, admin_id INT NULL, country_id INT NULL, city_id BIGINT NULL, type VARCHAR(80), title VARCHAR(255), message TEXT, icon VARCHAR(80), priority VARCHAR(40) DEFAULT 'normal', action_url VARCHAR(500), is_read TINYINT(1) DEFAULT 0, read_at DATETIME NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, scheduled_at DATETIME NULL, expires_at DATETIME NULL, INDEX(user_id,is_read), INDEX(type), INDEX(created_at)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_notifications (fresh installer safe).
CALL mj_safe_add_column('mj_notifications', 'user_id', '`user_id` BIGINT NULL');
CALL mj_safe_add_column('mj_notifications', 'company_id', '`company_id` BIGINT NULL');
CALL mj_safe_add_column('mj_notifications', 'admin_id', '`admin_id` INT NULL');
CALL mj_safe_add_column('mj_notifications', 'country_id', '`country_id` INT NULL');
CALL mj_safe_add_column('mj_notifications', 'city_id', '`city_id` BIGINT NULL');
CALL mj_safe_add_column('mj_notifications', 'type', '`type` VARCHAR(80)');
CALL mj_safe_add_column('mj_notifications', 'title', '`title` VARCHAR(255)');
CALL mj_safe_add_column('mj_notifications', 'message', '`message` TEXT');
CALL mj_safe_add_column('mj_notifications', 'icon', '`icon` VARCHAR(80)');
CALL mj_safe_add_column('mj_notifications', 'priority', '`priority` VARCHAR(40) DEFAULT ''normal''');
CALL mj_safe_add_column('mj_notifications', 'action_url', '`action_url` VARCHAR(500)');
CALL mj_safe_add_column('mj_notifications', 'is_read', '`is_read` TINYINT(1) DEFAULT 0');
CALL mj_safe_add_column('mj_notifications', 'read_at', '`read_at` DATETIME NULL');
CALL mj_safe_add_column('mj_notifications', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');
CALL mj_safe_add_column('mj_notifications', 'scheduled_at', '`scheduled_at` DATETIME NULL');
CALL mj_safe_add_column('mj_notifications', 'expires_at', '`expires_at` DATETIME NULL');

CREATE TABLE IF NOT EXISTS mj_social_providers (id INT AUTO_INCREMENT PRIMARY KEY, provider VARCHAR(50) UNIQUE, client_id TEXT NULL, client_secret TEXT NULL, redirect_url TEXT NULL, is_enabled TINYINT(1) DEFAULT 0, settings_json JSON NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_social_providers (fresh installer safe).
CALL mj_safe_add_column('mj_social_providers', 'provider', '`provider` VARCHAR(50) UNIQUE');
CALL mj_safe_add_column('mj_social_providers', 'client_id', '`client_id` TEXT NULL');
CALL mj_safe_add_column('mj_social_providers', 'client_secret', '`client_secret` TEXT NULL');
CALL mj_safe_add_column('mj_social_providers', 'redirect_url', '`redirect_url` TEXT NULL');
CALL mj_safe_add_column('mj_social_providers', 'is_enabled', '`is_enabled` TINYINT(1) DEFAULT 0');
CALL mj_safe_add_column('mj_social_providers', 'settings_json', '`settings_json` JSON NULL');
CALL mj_safe_add_column('mj_social_providers', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');

CREATE TABLE IF NOT EXISTS mj_user_social_accounts (id BIGINT AUTO_INCREMENT PRIMARY KEY, user_id BIGINT NOT NULL, provider VARCHAR(50) NOT NULL, provider_user_id VARCHAR(191) NOT NULL, provider_email VARCHAR(191), provider_name VARCHAR(191), provider_avatar TEXT, access_token TEXT, refresh_token TEXT, token_expires_at DATETIME NULL, linked_at DATETIME DEFAULT CURRENT_TIMESTAMP, last_login_at DATETIME NULL, UNIQUE KEY unique_provider_user(provider, provider_user_id), INDEX(user_id), INDEX(provider_email)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_user_social_accounts (fresh installer safe).
CALL mj_safe_add_column('mj_user_social_accounts', 'user_id', '`user_id` BIGINT NOT NULL');
CALL mj_safe_add_column('mj_user_social_accounts', 'provider', '`provider` VARCHAR(50) NOT NULL');
CALL mj_safe_add_column('mj_user_social_accounts', 'provider_user_id', '`provider_user_id` VARCHAR(191) NOT NULL');
CALL mj_safe_add_column('mj_user_social_accounts', 'provider_email', '`provider_email` VARCHAR(191)');
CALL mj_safe_add_column('mj_user_social_accounts', 'provider_name', '`provider_name` VARCHAR(191)');
CALL mj_safe_add_column('mj_user_social_accounts', 'provider_avatar', '`provider_avatar` TEXT');
CALL mj_safe_add_column('mj_user_social_accounts', 'access_token', '`access_token` TEXT');
CALL mj_safe_add_column('mj_user_social_accounts', 'refresh_token', '`refresh_token` TEXT');
CALL mj_safe_add_column('mj_user_social_accounts', 'token_expires_at', '`token_expires_at` DATETIME NULL');
CALL mj_safe_add_column('mj_user_social_accounts', 'linked_at', '`linked_at` DATETIME DEFAULT CURRENT_TIMESTAMP');
CALL mj_safe_add_column('mj_user_social_accounts', 'last_login_at', '`last_login_at` DATETIME NULL');

CREATE TABLE IF NOT EXISTS mj_category_fields (id BIGINT AUTO_INCREMENT PRIMARY KEY, category_id INT NULL, applies_to VARCHAR(40) DEFAULT 'listing', field_key VARCHAR(120), field_label VARCHAR(190), field_type VARCHAR(50), is_required TINYINT(1) DEFAULT 0, is_searchable TINYINT(1) DEFAULT 0, show_on_card TINYINT(1) DEFAULT 0, sort_order INT DEFAULT 0, help_text TEXT NULL, validation_json JSON NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX(category_id), INDEX(applies_to)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_category_fields (fresh installer safe).
CALL mj_safe_add_column('mj_category_fields', 'category_id', '`category_id` INT NULL');
CALL mj_safe_add_column('mj_category_fields', 'applies_to', '`applies_to` VARCHAR(40) DEFAULT ''listing''');
CALL mj_safe_add_column('mj_category_fields', 'field_key', '`field_key` VARCHAR(120)');
CALL mj_safe_add_column('mj_category_fields', 'field_label', '`field_label` VARCHAR(190)');
CALL mj_safe_add_column('mj_category_fields', 'field_type', '`field_type` VARCHAR(50)');
CALL mj_safe_add_column('mj_category_fields', 'is_required', '`is_required` TINYINT(1) DEFAULT 0');
CALL mj_safe_add_column('mj_category_fields', 'is_searchable', '`is_searchable` TINYINT(1) DEFAULT 0');
CALL mj_safe_add_column('mj_category_fields', 'show_on_card', '`show_on_card` TINYINT(1) DEFAULT 0');
CALL mj_safe_add_column('mj_category_fields', 'sort_order', '`sort_order` INT DEFAULT 0');
CALL mj_safe_add_column('mj_category_fields', 'help_text', '`help_text` TEXT NULL');
CALL mj_safe_add_column('mj_category_fields', 'validation_json', '`validation_json` JSON NULL');
CALL mj_safe_add_column('mj_category_fields', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');

CREATE TABLE IF NOT EXISTS mj_category_field_options (id BIGINT AUTO_INCREMENT PRIMARY KEY, field_id BIGINT NOT NULL, option_label VARCHAR(180), option_value VARCHAR(180), sort_order INT DEFAULT 0, INDEX(field_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_category_field_options (fresh installer safe).
CALL mj_safe_add_column('mj_category_field_options', 'field_id', '`field_id` BIGINT NOT NULL');
CALL mj_safe_add_column('mj_category_field_options', 'option_label', '`option_label` VARCHAR(180)');
CALL mj_safe_add_column('mj_category_field_options', 'option_value', '`option_value` VARCHAR(180)');
CALL mj_safe_add_column('mj_category_field_options', 'sort_order', '`sort_order` INT DEFAULT 0');

CREATE TABLE IF NOT EXISTS mj_dynamic_field_values (id BIGINT AUTO_INCREMENT PRIMARY KEY, related_type VARCHAR(40), related_id BIGINT, field_id BIGINT, field_key VARCHAR(120), field_value TEXT, INDEX(related_type, related_id), INDEX(field_key)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_dynamic_field_values (fresh installer safe).
CALL mj_safe_add_column('mj_dynamic_field_values', 'related_type', '`related_type` VARCHAR(40)');
CALL mj_safe_add_column('mj_dynamic_field_values', 'related_id', '`related_id` BIGINT');
CALL mj_safe_add_column('mj_dynamic_field_values', 'field_id', '`field_id` BIGINT');
CALL mj_safe_add_column('mj_dynamic_field_values', 'field_key', '`field_key` VARCHAR(120)');
CALL mj_safe_add_column('mj_dynamic_field_values', 'field_value', '`field_value` TEXT');

CREATE TABLE IF NOT EXISTS mj_seo_pages (id BIGINT AUTO_INCREMENT PRIMARY KEY, page_type VARCHAR(80), related_type VARCHAR(80), related_id BIGINT NULL, country_id INT NULL, city_id BIGINT NULL, slug VARCHAR(255), meta_title VARCHAR(255), meta_description TEXT, h1 VARCHAR(255), content_top MEDIUMTEXT, content_bottom MEDIUMTEXT, canonical_url TEXT, og_image VARCHAR(255), noindex TINYINT(1) DEFAULT 0, is_active TINYINT(1) DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX(page_type), INDEX(slug), INDEX(country_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_seo_pages (fresh installer safe).
CALL mj_safe_add_column('mj_seo_pages', 'page_type', '`page_type` VARCHAR(80)');
CALL mj_safe_add_column('mj_seo_pages', 'related_type', '`related_type` VARCHAR(80)');
CALL mj_safe_add_column('mj_seo_pages', 'related_id', '`related_id` BIGINT NULL');
CALL mj_safe_add_column('mj_seo_pages', 'country_id', '`country_id` INT NULL');
CALL mj_safe_add_column('mj_seo_pages', 'city_id', '`city_id` BIGINT NULL');
CALL mj_safe_add_column('mj_seo_pages', 'slug', '`slug` VARCHAR(255)');
CALL mj_safe_add_column('mj_seo_pages', 'meta_title', '`meta_title` VARCHAR(255)');
CALL mj_safe_add_column('mj_seo_pages', 'meta_description', '`meta_description` TEXT');
CALL mj_safe_add_column('mj_seo_pages', 'h1', '`h1` VARCHAR(255)');
CALL mj_safe_add_column('mj_seo_pages', 'content_top', '`content_top` MEDIUMTEXT');
CALL mj_safe_add_column('mj_seo_pages', 'content_bottom', '`content_bottom` MEDIUMTEXT');
CALL mj_safe_add_column('mj_seo_pages', 'canonical_url', '`canonical_url` TEXT');
CALL mj_safe_add_column('mj_seo_pages', 'og_image', '`og_image` VARCHAR(255)');
CALL mj_safe_add_column('mj_seo_pages', 'noindex', '`noindex` TINYINT(1) DEFAULT 0');
CALL mj_safe_add_column('mj_seo_pages', 'is_active', '`is_active` TINYINT(1) DEFAULT 1');
CALL mj_safe_add_column('mj_seo_pages', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');
CALL mj_safe_add_column('mj_seo_pages', 'updated_at', '`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');

CREATE TABLE IF NOT EXISTS mj_seo_redirects (id BIGINT AUTO_INCREMENT PRIMARY KEY, old_url VARCHAR(500), new_url VARCHAR(500), status_code INT DEFAULT 301, hit_count BIGINT DEFAULT 0, is_active TINYINT(1) DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX(old_url(191))) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_seo_redirects (fresh installer safe).
CALL mj_safe_add_column('mj_seo_redirects', 'old_url', '`old_url` VARCHAR(500)');
CALL mj_safe_add_column('mj_seo_redirects', 'new_url', '`new_url` VARCHAR(500)');
CALL mj_safe_add_column('mj_seo_redirects', 'status_code', '`status_code` INT DEFAULT 301');
CALL mj_safe_add_column('mj_seo_redirects', 'hit_count', '`hit_count` BIGINT DEFAULT 0');
CALL mj_safe_add_column('mj_seo_redirects', 'is_active', '`is_active` TINYINT(1) DEFAULT 1');
CALL mj_safe_add_column('mj_seo_redirects', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');

CREATE TABLE IF NOT EXISTS mj_sitemap_logs (id BIGINT AUTO_INCREMENT PRIMARY KEY, sitemap_type VARCHAR(80), url_count INT DEFAULT 0, file_path VARCHAR(255), status VARCHAR(50), message TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_sitemap_logs (fresh installer safe).
CALL mj_safe_add_column('mj_sitemap_logs', 'sitemap_type', '`sitemap_type` VARCHAR(80)');
CALL mj_safe_add_column('mj_sitemap_logs', 'url_count', '`url_count` INT DEFAULT 0');
CALL mj_safe_add_column('mj_sitemap_logs', 'file_path', '`file_path` VARCHAR(255)');
CALL mj_safe_add_column('mj_sitemap_logs', 'status', '`status` VARCHAR(50)');
CALL mj_safe_add_column('mj_sitemap_logs', 'message', '`message` TEXT');
CALL mj_safe_add_column('mj_sitemap_logs', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');

CREATE TABLE IF NOT EXISTS mj_ad_placements (id INT AUTO_INCREMENT PRIMARY KEY, placement_key VARCHAR(120) UNIQUE, placement_name VARCHAR(160), page_type VARCHAR(80), ad_size VARCHAR(80), max_ads INT DEFAULT 1, rotate_ads TINYINT(1) DEFAULT 1, is_active TINYINT(1) DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_ad_placements (fresh installer safe).
CALL mj_safe_add_column('mj_ad_placements', 'placement_key', '`placement_key` VARCHAR(120) UNIQUE');
CALL mj_safe_add_column('mj_ad_placements', 'placement_name', '`placement_name` VARCHAR(160)');
CALL mj_safe_add_column('mj_ad_placements', 'page_type', '`page_type` VARCHAR(80)');
CALL mj_safe_add_column('mj_ad_placements', 'ad_size', '`ad_size` VARCHAR(80)');
CALL mj_safe_add_column('mj_ad_placements', 'max_ads', '`max_ads` INT DEFAULT 1');
CALL mj_safe_add_column('mj_ad_placements', 'rotate_ads', '`rotate_ads` TINYINT(1) DEFAULT 1');
CALL mj_safe_add_column('mj_ad_placements', 'is_active', '`is_active` TINYINT(1) DEFAULT 1');
CALL mj_safe_add_column('mj_ad_placements', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');

CREATE TABLE IF NOT EXISTS mj_ad_packages (id INT AUTO_INCREMENT PRIMARY KEY, package_name VARCHAR(160), package_type VARCHAR(80), duration_days INT DEFAULT 7, price DECIMAL(12,2) DEFAULT 0, currency_code VARCHAR(8) DEFAULT 'SGD', placement_type VARCHAR(120), priority_level INT DEFAULT 0, is_active TINYINT(1) DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_ad_packages (fresh installer safe).
CALL mj_safe_add_column('mj_ad_packages', 'package_name', '`package_name` VARCHAR(160)');
CALL mj_safe_add_column('mj_ad_packages', 'package_type', '`package_type` VARCHAR(80)');
CALL mj_safe_add_column('mj_ad_packages', 'duration_days', '`duration_days` INT DEFAULT 7');
CALL mj_safe_add_column('mj_ad_packages', 'price', '`price` DECIMAL(12,2) DEFAULT 0');
CALL mj_safe_add_column('mj_ad_packages', 'currency_code', '`currency_code` VARCHAR(8) DEFAULT ''SGD''');
CALL mj_safe_add_column('mj_ad_packages', 'placement_type', '`placement_type` VARCHAR(120)');
CALL mj_safe_add_column('mj_ad_packages', 'priority_level', '`priority_level` INT DEFAULT 0');
CALL mj_safe_add_column('mj_ad_packages', 'is_active', '`is_active` TINYINT(1) DEFAULT 1');
CALL mj_safe_add_column('mj_ad_packages', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');

CREATE TABLE IF NOT EXISTS mj_sponsored_ads (id BIGINT AUTO_INCREMENT PRIMARY KEY, owner_user_id BIGINT NULL, company_id BIGINT NULL, related_type VARCHAR(80), related_id BIGINT NULL, placement_id INT NULL, title VARCHAR(255), image_path VARCHAR(255), target_url VARCHAR(500), country_id INT NULL, city_id BIGINT NULL, start_datetime DATETIME NULL, end_datetime DATETIME NULL, timezone VARCHAR(80) DEFAULT 'UTC', status ENUM('pending','active','scheduled','expired','paused','rejected','cancelled') DEFAULT 'pending', payment_status VARCHAR(60) DEFAULT 'unpaid', priority INT DEFAULT 0, impressions BIGINT DEFAULT 0, clicks BIGINT DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX(status), INDEX(end_datetime), INDEX(country_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_sponsored_ads (fresh installer safe).
CALL mj_safe_add_column('mj_sponsored_ads', 'owner_user_id', '`owner_user_id` BIGINT NULL');
CALL mj_safe_add_column('mj_sponsored_ads', 'company_id', '`company_id` BIGINT NULL');
CALL mj_safe_add_column('mj_sponsored_ads', 'related_type', '`related_type` VARCHAR(80)');
CALL mj_safe_add_column('mj_sponsored_ads', 'related_id', '`related_id` BIGINT NULL');
CALL mj_safe_add_column('mj_sponsored_ads', 'placement_id', '`placement_id` INT NULL');
CALL mj_safe_add_column('mj_sponsored_ads', 'title', '`title` VARCHAR(255)');
CALL mj_safe_add_column('mj_sponsored_ads', 'image_path', '`image_path` VARCHAR(255)');
CALL mj_safe_add_column('mj_sponsored_ads', 'target_url', '`target_url` VARCHAR(500)');
CALL mj_safe_add_column('mj_sponsored_ads', 'country_id', '`country_id` INT NULL');
CALL mj_safe_add_column('mj_sponsored_ads', 'city_id', '`city_id` BIGINT NULL');
CALL mj_safe_add_column('mj_sponsored_ads', 'start_datetime', '`start_datetime` DATETIME NULL');
CALL mj_safe_add_column('mj_sponsored_ads', 'end_datetime', '`end_datetime` DATETIME NULL');
CALL mj_safe_add_column('mj_sponsored_ads', 'timezone', '`timezone` VARCHAR(80) DEFAULT ''UTC''');
CALL mj_safe_add_column('mj_sponsored_ads', 'status', '`status` ENUM(''pending'',''active'',''scheduled'',''expired'',''paused'',''rejected'',''cancelled'') DEFAULT ''pending''');
CALL mj_safe_add_column('mj_sponsored_ads', 'payment_status', '`payment_status` VARCHAR(60) DEFAULT ''unpaid''');
CALL mj_safe_add_column('mj_sponsored_ads', 'priority', '`priority` INT DEFAULT 0');
CALL mj_safe_add_column('mj_sponsored_ads', 'impressions', '`impressions` BIGINT DEFAULT 0');
CALL mj_safe_add_column('mj_sponsored_ads', 'clicks', '`clicks` BIGINT DEFAULT 0');
CALL mj_safe_add_column('mj_sponsored_ads', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');

CREATE TABLE IF NOT EXISTS mj_ad_impressions (id BIGINT AUTO_INCREMENT PRIMARY KEY, ad_id BIGINT, ip_address VARCHAR(80), page_url TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX(ad_id), INDEX(created_at)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_ad_impressions (fresh installer safe).
CALL mj_safe_add_column('mj_ad_impressions', 'ad_id', '`ad_id` BIGINT');
CALL mj_safe_add_column('mj_ad_impressions', 'ip_address', '`ip_address` VARCHAR(80)');
CALL mj_safe_add_column('mj_ad_impressions', 'page_url', '`page_url` TEXT');
CALL mj_safe_add_column('mj_ad_impressions', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');

CREATE TABLE IF NOT EXISTS mj_ad_clicks (id BIGINT AUTO_INCREMENT PRIMARY KEY, ad_id BIGINT, ip_address VARCHAR(80), page_url TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX(ad_id), INDEX(created_at)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_ad_clicks (fresh installer safe).
CALL mj_safe_add_column('mj_ad_clicks', 'ad_id', '`ad_id` BIGINT');
CALL mj_safe_add_column('mj_ad_clicks', 'ip_address', '`ip_address` VARCHAR(80)');
CALL mj_safe_add_column('mj_ad_clicks', 'page_url', '`page_url` TEXT');
CALL mj_safe_add_column('mj_ad_clicks', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');

CREATE TABLE IF NOT EXISTS mj_deal_categories (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(160), slug VARCHAR(180), icon VARCHAR(80), is_active TINYINT(1) DEFAULT 1, sort_order INT DEFAULT 0) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_deal_categories (fresh installer safe).
CALL mj_safe_add_column('mj_deal_categories', 'name', '`name` VARCHAR(160)');
CALL mj_safe_add_column('mj_deal_categories', 'slug', '`slug` VARCHAR(180)');
CALL mj_safe_add_column('mj_deal_categories', 'icon', '`icon` VARCHAR(80)');
CALL mj_safe_add_column('mj_deal_categories', 'is_active', '`is_active` TINYINT(1) DEFAULT 1');
CALL mj_safe_add_column('mj_deal_categories', 'sort_order', '`sort_order` INT DEFAULT 0');

CREATE TABLE IF NOT EXISTS mj_deals (id BIGINT AUTO_INCREMENT PRIMARY KEY, company_id BIGINT NULL, category_id INT NULL, country_id INT NULL, city_id BIGINT NULL, title VARCHAR(255), slug VARCHAR(255), description MEDIUMTEXT, image_path VARCHAR(255), original_price DECIMAL(12,2) DEFAULT 0, deal_price DECIMAL(12,2) DEFAULT 0, discount_percent DECIMAL(8,2) DEFAULT 0, currency_code VARCHAR(8) DEFAULT 'SGD', quantity_total INT DEFAULT 0, quantity_sold INT DEFAULT 0, start_at DATETIME NULL, end_at DATETIME NULL, status ENUM('draft','pending','active','expired','rejected') DEFAULT 'pending', is_featured TINYINT(1) DEFAULT 0, is_sponsored TINYINT(1) DEFAULT 0, sponsored_until DATETIME NULL, terms TEXT NULL, redemption_instructions TEXT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX(country_id,status), INDEX(end_at), FULLTEXT KEY ft_deals(title,description)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_deals (fresh installer safe).
CALL mj_safe_add_column('mj_deals', 'company_id', '`company_id` BIGINT NULL');
CALL mj_safe_add_column('mj_deals', 'category_id', '`category_id` INT NULL');
CALL mj_safe_add_column('mj_deals', 'country_id', '`country_id` INT NULL');
CALL mj_safe_add_column('mj_deals', 'city_id', '`city_id` BIGINT NULL');
CALL mj_safe_add_column('mj_deals', 'title', '`title` VARCHAR(255)');
CALL mj_safe_add_column('mj_deals', 'slug', '`slug` VARCHAR(255)');
CALL mj_safe_add_column('mj_deals', 'description', '`description` MEDIUMTEXT');
CALL mj_safe_add_column('mj_deals', 'image_path', '`image_path` VARCHAR(255)');
CALL mj_safe_add_column('mj_deals', 'original_price', '`original_price` DECIMAL(12,2) DEFAULT 0');
CALL mj_safe_add_column('mj_deals', 'deal_price', '`deal_price` DECIMAL(12,2) DEFAULT 0');
CALL mj_safe_add_column('mj_deals', 'discount_percent', '`discount_percent` DECIMAL(8,2) DEFAULT 0');
CALL mj_safe_add_column('mj_deals', 'currency_code', '`currency_code` VARCHAR(8) DEFAULT ''SGD''');
CALL mj_safe_add_column('mj_deals', 'quantity_total', '`quantity_total` INT DEFAULT 0');
CALL mj_safe_add_column('mj_deals', 'quantity_sold', '`quantity_sold` INT DEFAULT 0');
CALL mj_safe_add_column('mj_deals', 'start_at', '`start_at` DATETIME NULL');
CALL mj_safe_add_column('mj_deals', 'end_at', '`end_at` DATETIME NULL');
CALL mj_safe_add_column('mj_deals', 'status', '`status` ENUM(''draft'',''pending'',''active'',''expired'',''rejected'') DEFAULT ''pending''');
CALL mj_safe_add_column('mj_deals', 'is_featured', '`is_featured` TINYINT(1) DEFAULT 0');
CALL mj_safe_add_column('mj_deals', 'is_sponsored', '`is_sponsored` TINYINT(1) DEFAULT 0');
CALL mj_safe_add_column('mj_deals', 'sponsored_until', '`sponsored_until` DATETIME NULL');
CALL mj_safe_add_column('mj_deals', 'terms', '`terms` TEXT NULL');
CALL mj_safe_add_column('mj_deals', 'redemption_instructions', '`redemption_instructions` TEXT NULL');
CALL mj_safe_add_column('mj_deals', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');
CALL mj_safe_add_column('mj_deals', 'updated_at', '`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');

CREATE TABLE IF NOT EXISTS mj_deal_purchases (id BIGINT AUTO_INCREMENT PRIMARY KEY, deal_id BIGINT, user_id BIGINT, quantity INT DEFAULT 1, total_amount DECIMAL(12,2), payment_status VARCHAR(60) DEFAULT 'pending', status VARCHAR(60) DEFAULT 'pending', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX(deal_id), INDEX(user_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_deal_purchases (fresh installer safe).
CALL mj_safe_add_column('mj_deal_purchases', 'deal_id', '`deal_id` BIGINT');
CALL mj_safe_add_column('mj_deal_purchases', 'user_id', '`user_id` BIGINT');
CALL mj_safe_add_column('mj_deal_purchases', 'quantity', '`quantity` INT DEFAULT 1');
CALL mj_safe_add_column('mj_deal_purchases', 'total_amount', '`total_amount` DECIMAL(12,2)');
CALL mj_safe_add_column('mj_deal_purchases', 'payment_status', '`payment_status` VARCHAR(60) DEFAULT ''pending''');
CALL mj_safe_add_column('mj_deal_purchases', 'status', '`status` VARCHAR(60) DEFAULT ''pending''');
CALL mj_safe_add_column('mj_deal_purchases', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');

CREATE TABLE IF NOT EXISTS mj_deal_vouchers (id BIGINT AUTO_INCREMENT PRIMARY KEY, purchase_id BIGINT, deal_id BIGINT, user_id BIGINT, voucher_code VARCHAR(80) UNIQUE, qr_text TEXT, status ENUM('purchased','active','redeemed','partially_redeemed','expired','cancelled','refunded','disputed') DEFAULT 'active', expires_at DATETIME NULL, redeemed_at DATETIME NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX(voucher_code), INDEX(user_id,status)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_deal_vouchers (fresh installer safe).
CALL mj_safe_add_column('mj_deal_vouchers', 'purchase_id', '`purchase_id` BIGINT');
CALL mj_safe_add_column('mj_deal_vouchers', 'deal_id', '`deal_id` BIGINT');
CALL mj_safe_add_column('mj_deal_vouchers', 'user_id', '`user_id` BIGINT');
CALL mj_safe_add_column('mj_deal_vouchers', 'voucher_code', '`voucher_code` VARCHAR(80) UNIQUE');
CALL mj_safe_add_column('mj_deal_vouchers', 'qr_text', '`qr_text` TEXT');
CALL mj_safe_add_column('mj_deal_vouchers', 'status', '`status` ENUM(''purchased'',''active'',''redeemed'',''partially_redeemed'',''expired'',''cancelled'',''refunded'',''disputed'') DEFAULT ''active''');
CALL mj_safe_add_column('mj_deal_vouchers', 'expires_at', '`expires_at` DATETIME NULL');
CALL mj_safe_add_column('mj_deal_vouchers', 'redeemed_at', '`redeemed_at` DATETIME NULL');
CALL mj_safe_add_column('mj_deal_vouchers', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');

CREATE TABLE IF NOT EXISTS mj_deal_redemptions (id BIGINT AUTO_INCREMENT PRIMARY KEY, voucher_id BIGINT, company_id BIGINT NULL, branch_id BIGINT NULL, staff_id BIGINT NULL, redeem_note TEXT, ip_address VARCHAR(80), created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX(voucher_id), INDEX(company_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_deal_redemptions (fresh installer safe).
CALL mj_safe_add_column('mj_deal_redemptions', 'voucher_id', '`voucher_id` BIGINT');
CALL mj_safe_add_column('mj_deal_redemptions', 'company_id', '`company_id` BIGINT NULL');
CALL mj_safe_add_column('mj_deal_redemptions', 'branch_id', '`branch_id` BIGINT NULL');
CALL mj_safe_add_column('mj_deal_redemptions', 'staff_id', '`staff_id` BIGINT NULL');
CALL mj_safe_add_column('mj_deal_redemptions', 'redeem_note', '`redeem_note` TEXT');
CALL mj_safe_add_column('mj_deal_redemptions', 'ip_address', '`ip_address` VARCHAR(80)');
CALL mj_safe_add_column('mj_deal_redemptions', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');

CREATE TABLE IF NOT EXISTS mj_deal_refunds (id BIGINT AUTO_INCREMENT PRIMARY KEY, purchase_id BIGINT, voucher_id BIGINT NULL, user_id BIGINT, reason TEXT, status VARCHAR(60) DEFAULT 'pending', amount DECIMAL(12,2) DEFAULT 0, admin_note TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_deal_refunds (fresh installer safe).
CALL mj_safe_add_column('mj_deal_refunds', 'purchase_id', '`purchase_id` BIGINT');
CALL mj_safe_add_column('mj_deal_refunds', 'voucher_id', '`voucher_id` BIGINT NULL');
CALL mj_safe_add_column('mj_deal_refunds', 'user_id', '`user_id` BIGINT');
CALL mj_safe_add_column('mj_deal_refunds', 'reason', '`reason` TEXT');
CALL mj_safe_add_column('mj_deal_refunds', 'status', '`status` VARCHAR(60) DEFAULT ''pending''');
CALL mj_safe_add_column('mj_deal_refunds', 'amount', '`amount` DECIMAL(12,2) DEFAULT 0');
CALL mj_safe_add_column('mj_deal_refunds', 'admin_note', '`admin_note` TEXT');
CALL mj_safe_add_column('mj_deal_refunds', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');

CREATE TABLE IF NOT EXISTS mj_deal_payouts (id BIGINT AUTO_INCREMENT PRIMARY KEY, company_id BIGINT, period_start DATE, period_end DATE, gross_amount DECIMAL(12,2) DEFAULT 0, commission_amount DECIMAL(12,2) DEFAULT 0, refund_amount DECIMAL(12,2) DEFAULT 0, net_amount DECIMAL(12,2) DEFAULT 0, status VARCHAR(60) DEFAULT 'pending', created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_deal_payouts (fresh installer safe).
CALL mj_safe_add_column('mj_deal_payouts', 'company_id', '`company_id` BIGINT');
CALL mj_safe_add_column('mj_deal_payouts', 'period_start', '`period_start` DATE');
CALL mj_safe_add_column('mj_deal_payouts', 'period_end', '`period_end` DATE');
CALL mj_safe_add_column('mj_deal_payouts', 'gross_amount', '`gross_amount` DECIMAL(12,2) DEFAULT 0');
CALL mj_safe_add_column('mj_deal_payouts', 'commission_amount', '`commission_amount` DECIMAL(12,2) DEFAULT 0');
CALL mj_safe_add_column('mj_deal_payouts', 'refund_amount', '`refund_amount` DECIMAL(12,2) DEFAULT 0');
CALL mj_safe_add_column('mj_deal_payouts', 'net_amount', '`net_amount` DECIMAL(12,2) DEFAULT 0');
CALL mj_safe_add_column('mj_deal_payouts', 'status', '`status` VARCHAR(60) DEFAULT ''pending''');
CALL mj_safe_add_column('mj_deal_payouts', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');

CREATE TABLE IF NOT EXISTS mj_deal_commissions (id INT AUTO_INCREMENT PRIMARY KEY, rule_name VARCHAR(160), country_id INT NULL, category_id INT NULL, company_id BIGINT NULL, commission_type VARCHAR(40) DEFAULT 'percent', commission_value DECIMAL(12,2) DEFAULT 0, is_active TINYINT(1) DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_deal_commissions (fresh installer safe).
CALL mj_safe_add_column('mj_deal_commissions', 'rule_name', '`rule_name` VARCHAR(160)');
CALL mj_safe_add_column('mj_deal_commissions', 'country_id', '`country_id` INT NULL');
CALL mj_safe_add_column('mj_deal_commissions', 'category_id', '`category_id` INT NULL');
CALL mj_safe_add_column('mj_deal_commissions', 'company_id', '`company_id` BIGINT NULL');
CALL mj_safe_add_column('mj_deal_commissions', 'commission_type', '`commission_type` VARCHAR(40) DEFAULT ''percent''');
CALL mj_safe_add_column('mj_deal_commissions', 'commission_value', '`commission_value` DECIMAL(12,2) DEFAULT 0');
CALL mj_safe_add_column('mj_deal_commissions', 'is_active', '`is_active` TINYINT(1) DEFAULT 1');
CALL mj_safe_add_column('mj_deal_commissions', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');

CREATE TABLE IF NOT EXISTS mj_deal_reports (id BIGINT AUTO_INCREMENT PRIMARY KEY, deal_id BIGINT NULL, report_type VARCHAR(80), message TEXT, status VARCHAR(60) DEFAULT 'new', created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_deal_reports (fresh installer safe).
CALL mj_safe_add_column('mj_deal_reports', 'deal_id', '`deal_id` BIGINT NULL');
CALL mj_safe_add_column('mj_deal_reports', 'report_type', '`report_type` VARCHAR(80)');
CALL mj_safe_add_column('mj_deal_reports', 'message', '`message` TEXT');
CALL mj_safe_add_column('mj_deal_reports', 'status', '`status` VARCHAR(60) DEFAULT ''new''');
CALL mj_safe_add_column('mj_deal_reports', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');

CREATE TABLE IF NOT EXISTS mj_deal_favorites (id BIGINT AUTO_INCREMENT PRIMARY KEY, user_id BIGINT, deal_id BIGINT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uniq_deal_fav(user_id,deal_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_deal_favorites (fresh installer safe).
CALL mj_safe_add_column('mj_deal_favorites', 'user_id', '`user_id` BIGINT');
CALL mj_safe_add_column('mj_deal_favorites', 'deal_id', '`deal_id` BIGINT');
CALL mj_safe_add_column('mj_deal_favorites', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');

CREATE TABLE IF NOT EXISTS mj_deal_gifts (id BIGINT AUTO_INCREMENT PRIMARY KEY, purchase_id BIGINT, sender_user_id BIGINT, recipient_name VARCHAR(160), recipient_email VARCHAR(190), gift_message TEXT, send_at DATETIME NULL, status VARCHAR(60) DEFAULT 'pending', created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_deal_gifts (fresh installer safe).
CALL mj_safe_add_column('mj_deal_gifts', 'purchase_id', '`purchase_id` BIGINT');
CALL mj_safe_add_column('mj_deal_gifts', 'sender_user_id', '`sender_user_id` BIGINT');
CALL mj_safe_add_column('mj_deal_gifts', 'recipient_name', '`recipient_name` VARCHAR(160)');
CALL mj_safe_add_column('mj_deal_gifts', 'recipient_email', '`recipient_email` VARCHAR(190)');
CALL mj_safe_add_column('mj_deal_gifts', 'gift_message', '`gift_message` TEXT');
CALL mj_safe_add_column('mj_deal_gifts', 'send_at', '`send_at` DATETIME NULL');
CALL mj_safe_add_column('mj_deal_gifts', 'status', '`status` VARCHAR(60) DEFAULT ''pending''');
CALL mj_safe_add_column('mj_deal_gifts', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');

CREATE TABLE IF NOT EXISTS mj_promotions (id BIGINT AUTO_INCREMENT PRIMARY KEY, company_id BIGINT NULL, country_id INT NULL, city_id BIGINT NULL, title VARCHAR(255), slug VARCHAR(255), description TEXT, banner_image VARCHAR(255), promo_type VARCHAR(80), promo_code VARCHAR(80), discount_value DECIMAL(12,2) DEFAULT 0, start_at DATETIME NULL, end_at DATETIME NULL, status VARCHAR(60) DEFAULT 'pending', created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_promotions (fresh installer safe).
CALL mj_safe_add_column('mj_promotions', 'company_id', '`company_id` BIGINT NULL');
CALL mj_safe_add_column('mj_promotions', 'country_id', '`country_id` INT NULL');
CALL mj_safe_add_column('mj_promotions', 'city_id', '`city_id` BIGINT NULL');
CALL mj_safe_add_column('mj_promotions', 'title', '`title` VARCHAR(255)');
CALL mj_safe_add_column('mj_promotions', 'slug', '`slug` VARCHAR(255)');
CALL mj_safe_add_column('mj_promotions', 'description', '`description` TEXT');
CALL mj_safe_add_column('mj_promotions', 'banner_image', '`banner_image` VARCHAR(255)');
CALL mj_safe_add_column('mj_promotions', 'promo_type', '`promo_type` VARCHAR(80)');
CALL mj_safe_add_column('mj_promotions', 'promo_code', '`promo_code` VARCHAR(80)');
CALL mj_safe_add_column('mj_promotions', 'discount_value', '`discount_value` DECIMAL(12,2) DEFAULT 0');
CALL mj_safe_add_column('mj_promotions', 'start_at', '`start_at` DATETIME NULL');
CALL mj_safe_add_column('mj_promotions', 'end_at', '`end_at` DATETIME NULL');
CALL mj_safe_add_column('mj_promotions', 'status', '`status` VARCHAR(60) DEFAULT ''pending''');
CALL mj_safe_add_column('mj_promotions', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');

CREATE TABLE IF NOT EXISTS mj_coupons (id BIGINT AUTO_INCREMENT PRIMARY KEY, company_id BIGINT NULL, branch_id BIGINT NULL, country_id INT NULL, city_id BIGINT NULL, coupon_code VARCHAR(80) UNIQUE, title VARCHAR(255), discount_type VARCHAR(40), discount_value DECIMAL(12,2), min_spend DECIMAL(12,2) DEFAULT 0, max_discount DECIMAL(12,2) DEFAULT 0, usage_limit INT DEFAULT 0, per_user_limit INT DEFAULT 1, start_at DATETIME NULL, end_at DATETIME NULL, status VARCHAR(60) DEFAULT 'active', created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_coupons (fresh installer safe).
CALL mj_safe_add_column('mj_coupons', 'company_id', '`company_id` BIGINT NULL');
CALL mj_safe_add_column('mj_coupons', 'branch_id', '`branch_id` BIGINT NULL');
CALL mj_safe_add_column('mj_coupons', 'country_id', '`country_id` INT NULL');
CALL mj_safe_add_column('mj_coupons', 'city_id', '`city_id` BIGINT NULL');
CALL mj_safe_add_column('mj_coupons', 'coupon_code', '`coupon_code` VARCHAR(80) UNIQUE');
CALL mj_safe_add_column('mj_coupons', 'title', '`title` VARCHAR(255)');
CALL mj_safe_add_column('mj_coupons', 'discount_type', '`discount_type` VARCHAR(40)');
CALL mj_safe_add_column('mj_coupons', 'discount_value', '`discount_value` DECIMAL(12,2)');
CALL mj_safe_add_column('mj_coupons', 'min_spend', '`min_spend` DECIMAL(12,2) DEFAULT 0');
CALL mj_safe_add_column('mj_coupons', 'max_discount', '`max_discount` DECIMAL(12,2) DEFAULT 0');
CALL mj_safe_add_column('mj_coupons', 'usage_limit', '`usage_limit` INT DEFAULT 0');
CALL mj_safe_add_column('mj_coupons', 'per_user_limit', '`per_user_limit` INT DEFAULT 1');
CALL mj_safe_add_column('mj_coupons', 'start_at', '`start_at` DATETIME NULL');
CALL mj_safe_add_column('mj_coupons', 'end_at', '`end_at` DATETIME NULL');
CALL mj_safe_add_column('mj_coupons', 'status', '`status` VARCHAR(60) DEFAULT ''active''');
CALL mj_safe_add_column('mj_coupons', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');

CREATE TABLE IF NOT EXISTS mj_coupon_redemptions (id BIGINT AUTO_INCREMENT PRIMARY KEY, coupon_id BIGINT, user_id BIGINT NULL, related_type VARCHAR(80), related_id BIGINT NULL, redeemed_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX(coupon_id), INDEX(user_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_coupon_redemptions (fresh installer safe).
CALL mj_safe_add_column('mj_coupon_redemptions', 'coupon_id', '`coupon_id` BIGINT');
CALL mj_safe_add_column('mj_coupon_redemptions', 'user_id', '`user_id` BIGINT NULL');
CALL mj_safe_add_column('mj_coupon_redemptions', 'related_type', '`related_type` VARCHAR(80)');
CALL mj_safe_add_column('mj_coupon_redemptions', 'related_id', '`related_id` BIGINT NULL');
CALL mj_safe_add_column('mj_coupon_redemptions', 'redeemed_at', '`redeemed_at` DATETIME DEFAULT CURRENT_TIMESTAMP');

CREATE TABLE IF NOT EXISTS mj_quote_requests (id BIGINT AUTO_INCREMENT PRIMARY KEY, user_id BIGINT NULL, company_id BIGINT NULL, service_id BIGINT NULL, country_id INT NULL, city_id BIGINT NULL, title VARCHAR(255), description TEXT, urgency VARCHAR(80), status VARCHAR(60) DEFAULT 'new', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX(company_id,status), INDEX(user_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_quote_requests (fresh installer safe).
CALL mj_safe_add_column('mj_quote_requests', 'user_id', '`user_id` BIGINT NULL');
CALL mj_safe_add_column('mj_quote_requests', 'company_id', '`company_id` BIGINT NULL');
CALL mj_safe_add_column('mj_quote_requests', 'service_id', '`service_id` BIGINT NULL');
CALL mj_safe_add_column('mj_quote_requests', 'country_id', '`country_id` INT NULL');
CALL mj_safe_add_column('mj_quote_requests', 'city_id', '`city_id` BIGINT NULL');
CALL mj_safe_add_column('mj_quote_requests', 'title', '`title` VARCHAR(255)');
CALL mj_safe_add_column('mj_quote_requests', 'description', '`description` TEXT');
CALL mj_safe_add_column('mj_quote_requests', 'urgency', '`urgency` VARCHAR(80)');
CALL mj_safe_add_column('mj_quote_requests', 'status', '`status` VARCHAR(60) DEFAULT ''new''');
CALL mj_safe_add_column('mj_quote_requests', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');

CREATE TABLE IF NOT EXISTS mj_quotes (id BIGINT AUTO_INCREMENT PRIMARY KEY, quote_request_id BIGINT NULL, company_id BIGINT NULL, user_id BIGINT NULL, quote_no VARCHAR(80), amount DECIMAL(12,2) DEFAULT 0, valid_until DATE NULL, notes TEXT, status VARCHAR(60) DEFAULT 'sent', created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_quotes (fresh installer safe).
CALL mj_safe_add_column('mj_quotes', 'quote_request_id', '`quote_request_id` BIGINT NULL');
CALL mj_safe_add_column('mj_quotes', 'company_id', '`company_id` BIGINT NULL');
CALL mj_safe_add_column('mj_quotes', 'user_id', '`user_id` BIGINT NULL');
CALL mj_safe_add_column('mj_quotes', 'quote_no', '`quote_no` VARCHAR(80)');
CALL mj_safe_add_column('mj_quotes', 'amount', '`amount` DECIMAL(12,2) DEFAULT 0');
CALL mj_safe_add_column('mj_quotes', 'valid_until', '`valid_until` DATE NULL');
CALL mj_safe_add_column('mj_quotes', 'notes', '`notes` TEXT');
CALL mj_safe_add_column('mj_quotes', 'status', '`status` VARCHAR(60) DEFAULT ''sent''');
CALL mj_safe_add_column('mj_quotes', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');

CREATE TABLE IF NOT EXISTS mj_bookings (id BIGINT AUTO_INCREMENT PRIMARY KEY, user_id BIGINT NULL, company_id BIGINT NULL, branch_id BIGINT NULL, service_id BIGINT NULL, staff_id BIGINT NULL, booking_start DATETIME NULL, booking_end DATETIME NULL, timezone VARCHAR(80) DEFAULT 'UTC', status VARCHAR(60) DEFAULT 'pending', notes TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX(company_id,status), INDEX(user_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_bookings (fresh installer safe).
CALL mj_safe_add_column('mj_bookings', 'user_id', '`user_id` BIGINT NULL');
CALL mj_safe_add_column('mj_bookings', 'company_id', '`company_id` BIGINT NULL');
CALL mj_safe_add_column('mj_bookings', 'branch_id', '`branch_id` BIGINT NULL');
CALL mj_safe_add_column('mj_bookings', 'service_id', '`service_id` BIGINT NULL');
CALL mj_safe_add_column('mj_bookings', 'staff_id', '`staff_id` BIGINT NULL');
CALL mj_safe_add_column('mj_bookings', 'booking_start', '`booking_start` DATETIME NULL');
CALL mj_safe_add_column('mj_bookings', 'booking_end', '`booking_end` DATETIME NULL');
CALL mj_safe_add_column('mj_bookings', 'timezone', '`timezone` VARCHAR(80) DEFAULT ''UTC''');
CALL mj_safe_add_column('mj_bookings', 'status', '`status` VARCHAR(60) DEFAULT ''pending''');
CALL mj_safe_add_column('mj_bookings', 'notes', '`notes` TEXT');
CALL mj_safe_add_column('mj_bookings', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');

CREATE TABLE IF NOT EXISTS mj_company_staff (id BIGINT AUTO_INCREMENT PRIMARY KEY, company_id BIGINT, branch_id BIGINT NULL, user_id BIGINT NULL, name VARCHAR(160), email VARCHAR(190), role VARCHAR(80), permissions_json JSON NULL, is_active TINYINT(1) DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_company_staff (fresh installer safe).
CALL mj_safe_add_column('mj_company_staff', 'company_id', '`company_id` BIGINT');
CALL mj_safe_add_column('mj_company_staff', 'branch_id', '`branch_id` BIGINT NULL');
CALL mj_safe_add_column('mj_company_staff', 'user_id', '`user_id` BIGINT NULL');
CALL mj_safe_add_column('mj_company_staff', 'name', '`name` VARCHAR(160)');
CALL mj_safe_add_column('mj_company_staff', 'email', '`email` VARCHAR(190)');
CALL mj_safe_add_column('mj_company_staff', 'role', '`role` VARCHAR(80)');
CALL mj_safe_add_column('mj_company_staff', 'permissions_json', '`permissions_json` JSON NULL');
CALL mj_safe_add_column('mj_company_staff', 'is_active', '`is_active` TINYINT(1) DEFAULT 1');
CALL mj_safe_add_column('mj_company_staff', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');

CREATE TABLE IF NOT EXISTS mj_company_leads (id BIGINT AUTO_INCREMENT PRIMARY KEY, company_id BIGINT, branch_id BIGINT NULL, user_id BIGINT NULL, source VARCHAR(80), title VARCHAR(255), status VARCHAR(60) DEFAULT 'new', priority VARCHAR(40) DEFAULT 'normal', lead_value DECIMAL(12,2) DEFAULT 0, followup_at DATETIME NULL, assigned_staff_id BIGINT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX(company_id,status)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_company_leads (fresh installer safe).
CALL mj_safe_add_column('mj_company_leads', 'company_id', '`company_id` BIGINT');
CALL mj_safe_add_column('mj_company_leads', 'branch_id', '`branch_id` BIGINT NULL');
CALL mj_safe_add_column('mj_company_leads', 'user_id', '`user_id` BIGINT NULL');
CALL mj_safe_add_column('mj_company_leads', 'source', '`source` VARCHAR(80)');
CALL mj_safe_add_column('mj_company_leads', 'title', '`title` VARCHAR(255)');
CALL mj_safe_add_column('mj_company_leads', 'status', '`status` VARCHAR(60) DEFAULT ''new''');
CALL mj_safe_add_column('mj_company_leads', 'priority', '`priority` VARCHAR(40) DEFAULT ''normal''');
CALL mj_safe_add_column('mj_company_leads', 'lead_value', '`lead_value` DECIMAL(12,2) DEFAULT 0');
CALL mj_safe_add_column('mj_company_leads', 'followup_at', '`followup_at` DATETIME NULL');
CALL mj_safe_add_column('mj_company_leads', 'assigned_staff_id', '`assigned_staff_id` BIGINT NULL');
CALL mj_safe_add_column('mj_company_leads', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');

CREATE TABLE IF NOT EXISTS mj_company_lead_notes (id BIGINT AUTO_INCREMENT PRIMARY KEY, lead_id BIGINT, staff_id BIGINT NULL, note TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_company_lead_notes (fresh installer safe).
CALL mj_safe_add_column('mj_company_lead_notes', 'lead_id', '`lead_id` BIGINT');
CALL mj_safe_add_column('mj_company_lead_notes', 'staff_id', '`staff_id` BIGINT NULL');
CALL mj_safe_add_column('mj_company_lead_notes', 'note', '`note` TEXT');
CALL mj_safe_add_column('mj_company_lead_notes', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');

CREATE TABLE IF NOT EXISTS mj_company_documents (id BIGINT AUTO_INCREMENT PRIMARY KEY, company_id BIGINT, document_type VARCHAR(80), file_path VARCHAR(255), status VARCHAR(60) DEFAULT 'pending', expires_at DATE NULL, admin_note TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_company_documents (fresh installer safe).
CALL mj_safe_add_column('mj_company_documents', 'company_id', '`company_id` BIGINT');
CALL mj_safe_add_column('mj_company_documents', 'document_type', '`document_type` VARCHAR(80)');
CALL mj_safe_add_column('mj_company_documents', 'file_path', '`file_path` VARCHAR(255)');
CALL mj_safe_add_column('mj_company_documents', 'status', '`status` VARCHAR(60) DEFAULT ''pending''');
CALL mj_safe_add_column('mj_company_documents', 'expires_at', '`expires_at` DATE NULL');
CALL mj_safe_add_column('mj_company_documents', 'admin_note', '`admin_note` TEXT');
CALL mj_safe_add_column('mj_company_documents', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');

CREATE TABLE IF NOT EXISTS mj_company_analytics (id BIGINT AUTO_INCREMENT PRIMARY KEY, company_id BIGINT, metric_date DATE, views INT DEFAULT 0, phone_clicks INT DEFAULT 0, whatsapp_clicks INT DEFAULT 0, website_clicks INT DEFAULT 0, leads INT DEFAULT 0, bookings INT DEFAULT 0, deals_sold INT DEFAULT 0, revenue DECIMAL(12,2) DEFAULT 0, UNIQUE KEY uniq_company_metric(company_id,metric_date)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_company_analytics (fresh installer safe).
CALL mj_safe_add_column('mj_company_analytics', 'company_id', '`company_id` BIGINT');
CALL mj_safe_add_column('mj_company_analytics', 'metric_date', '`metric_date` DATE');
CALL mj_safe_add_column('mj_company_analytics', 'views', '`views` INT DEFAULT 0');
CALL mj_safe_add_column('mj_company_analytics', 'phone_clicks', '`phone_clicks` INT DEFAULT 0');
CALL mj_safe_add_column('mj_company_analytics', 'whatsapp_clicks', '`whatsapp_clicks` INT DEFAULT 0');
CALL mj_safe_add_column('mj_company_analytics', 'website_clicks', '`website_clicks` INT DEFAULT 0');
CALL mj_safe_add_column('mj_company_analytics', 'leads', '`leads` INT DEFAULT 0');
CALL mj_safe_add_column('mj_company_analytics', 'bookings', '`bookings` INT DEFAULT 0');
CALL mj_safe_add_column('mj_company_analytics', 'deals_sold', '`deals_sold` INT DEFAULT 0');
CALL mj_safe_add_column('mj_company_analytics', 'revenue', '`revenue` DECIMAL(12,2) DEFAULT 0');

CREATE TABLE IF NOT EXISTS mj_company_packages (id INT AUTO_INCREMENT PRIMARY KEY, package_name VARCHAR(160), price DECIMAL(12,2) DEFAULT 0, duration_days INT DEFAULT 30, branch_limit INT DEFAULT 1, staff_limit INT DEFAULT 1, service_limit INT DEFAULT 10, deal_limit INT DEFAULT 0, is_active TINYINT(1) DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_company_packages (fresh installer safe).
CALL mj_safe_add_column('mj_company_packages', 'package_name', '`package_name` VARCHAR(160)');
CALL mj_safe_add_column('mj_company_packages', 'price', '`price` DECIMAL(12,2) DEFAULT 0');
CALL mj_safe_add_column('mj_company_packages', 'duration_days', '`duration_days` INT DEFAULT 30');
CALL mj_safe_add_column('mj_company_packages', 'branch_limit', '`branch_limit` INT DEFAULT 1');
CALL mj_safe_add_column('mj_company_packages', 'staff_limit', '`staff_limit` INT DEFAULT 1');
CALL mj_safe_add_column('mj_company_packages', 'service_limit', '`service_limit` INT DEFAULT 10');
CALL mj_safe_add_column('mj_company_packages', 'deal_limit', '`deal_limit` INT DEFAULT 0');
CALL mj_safe_add_column('mj_company_packages', 'is_active', '`is_active` TINYINT(1) DEFAULT 1');
CALL mj_safe_add_column('mj_company_packages', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');

CREATE TABLE IF NOT EXISTS mj_company_subscriptions (id BIGINT AUTO_INCREMENT PRIMARY KEY, company_id BIGINT, package_id INT, start_at DATETIME, end_at DATETIME, status VARCHAR(60) DEFAULT 'active', created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_company_subscriptions (fresh installer safe).
CALL mj_safe_add_column('mj_company_subscriptions', 'company_id', '`company_id` BIGINT');
CALL mj_safe_add_column('mj_company_subscriptions', 'package_id', '`package_id` INT');
CALL mj_safe_add_column('mj_company_subscriptions', 'start_at', '`start_at` DATETIME');
CALL mj_safe_add_column('mj_company_subscriptions', 'end_at', '`end_at` DATETIME');
CALL mj_safe_add_column('mj_company_subscriptions', 'status', '`status` VARCHAR(60) DEFAULT ''active''');
CALL mj_safe_add_column('mj_company_subscriptions', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');

CREATE TABLE IF NOT EXISTS mj_company_claims (id BIGINT AUTO_INCREMENT PRIMARY KEY, company_id BIGINT NULL, seed_id BIGINT NULL, user_id BIGINT NULL, claimant_name VARCHAR(160), claimant_email VARCHAR(190), proof_file VARCHAR(255), status VARCHAR(60) DEFAULT 'pending', admin_note TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_company_claims (fresh installer safe).
CALL mj_safe_add_column('mj_company_claims', 'company_id', '`company_id` BIGINT NULL');
CALL mj_safe_add_column('mj_company_claims', 'seed_id', '`seed_id` BIGINT NULL');
CALL mj_safe_add_column('mj_company_claims', 'user_id', '`user_id` BIGINT NULL');
CALL mj_safe_add_column('mj_company_claims', 'claimant_name', '`claimant_name` VARCHAR(160)');
CALL mj_safe_add_column('mj_company_claims', 'claimant_email', '`claimant_email` VARCHAR(190)');
CALL mj_safe_add_column('mj_company_claims', 'proof_file', '`proof_file` VARCHAR(255)');
CALL mj_safe_add_column('mj_company_claims', 'status', '`status` VARCHAR(60) DEFAULT ''pending''');
CALL mj_safe_add_column('mj_company_claims', 'admin_note', '`admin_note` TEXT');
CALL mj_safe_add_column('mj_company_claims', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');

CREATE TABLE IF NOT EXISTS mj_wallet_transactions (id BIGINT AUTO_INCREMENT PRIMARY KEY, user_id BIGINT NULL, company_id BIGINT NULL, transaction_type VARCHAR(60), amount DECIMAL(12,2), currency_code VARCHAR(8) DEFAULT 'SGD', description TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_wallet_transactions (fresh installer safe).
CALL mj_safe_add_column('mj_wallet_transactions', 'user_id', '`user_id` BIGINT NULL');
CALL mj_safe_add_column('mj_wallet_transactions', 'company_id', '`company_id` BIGINT NULL');
CALL mj_safe_add_column('mj_wallet_transactions', 'transaction_type', '`transaction_type` VARCHAR(60)');
CALL mj_safe_add_column('mj_wallet_transactions', 'amount', '`amount` DECIMAL(12,2)');
CALL mj_safe_add_column('mj_wallet_transactions', 'currency_code', '`currency_code` VARCHAR(8) DEFAULT ''SGD''');
CALL mj_safe_add_column('mj_wallet_transactions', 'description', '`description` TEXT');
CALL mj_safe_add_column('mj_wallet_transactions', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');

CREATE TABLE IF NOT EXISTS mj_support_tickets (id BIGINT AUTO_INCREMENT PRIMARY KEY, user_id BIGINT NULL, company_id BIGINT NULL, subject VARCHAR(255), message TEXT, priority VARCHAR(40) DEFAULT 'normal', status VARCHAR(60) DEFAULT 'open', assigned_admin_id INT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_support_tickets (fresh installer safe).
CALL mj_safe_add_column('mj_support_tickets', 'user_id', '`user_id` BIGINT NULL');
CALL mj_safe_add_column('mj_support_tickets', 'company_id', '`company_id` BIGINT NULL');
CALL mj_safe_add_column('mj_support_tickets', 'subject', '`subject` VARCHAR(255)');
CALL mj_safe_add_column('mj_support_tickets', 'message', '`message` TEXT');
CALL mj_safe_add_column('mj_support_tickets', 'priority', '`priority` VARCHAR(40) DEFAULT ''normal''');
CALL mj_safe_add_column('mj_support_tickets', 'status', '`status` VARCHAR(60) DEFAULT ''open''');
CALL mj_safe_add_column('mj_support_tickets', 'assigned_admin_id', '`assigned_admin_id` INT NULL');
CALL mj_safe_add_column('mj_support_tickets', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');

CREATE TABLE IF NOT EXISTS mj_automation_rules (id INT AUTO_INCREMENT PRIMARY KEY, rule_name VARCHAR(160), event_type VARCHAR(100), condition_json JSON NULL, action_json JSON NULL, is_active TINYINT(1) DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_automation_rules (fresh installer safe).
CALL mj_safe_add_column('mj_automation_rules', 'rule_name', '`rule_name` VARCHAR(160)');
CALL mj_safe_add_column('mj_automation_rules', 'event_type', '`event_type` VARCHAR(100)');
CALL mj_safe_add_column('mj_automation_rules', 'condition_json', '`condition_json` JSON NULL');
CALL mj_safe_add_column('mj_automation_rules', 'action_json', '`action_json` JSON NULL');
CALL mj_safe_add_column('mj_automation_rules', 'is_active', '`is_active` TINYINT(1) DEFAULT 1');
CALL mj_safe_add_column('mj_automation_rules', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');

INSERT IGNORE INTO mj_social_providers(provider,is_enabled) VALUES('google',0),('facebook',0),('apple',0),('microsoft',0),('linkedin',0);
INSERT IGNORE INTO mj_ad_placements(placement_key,placement_name,page_type,ad_size,max_ads,rotate_ads,is_active) VALUES('homepage_top','Homepage Top Banner','home','970x250',3,1,1),('country_hero','Country Hero Banner','country','1080x300',2,1,1),('search_top','Search Result Top','search','responsive',5,1,1),('mobile_sticky','Mobile Sticky Bottom','mobile','320x100',1,0,1);
INSERT IGNORE INTO mj_ad_packages(package_name,package_type,duration_days,price,currency_code,placement_type,priority_level,is_active) VALUES('7 Days Featured Listing','listing',7,19.00,'SGD','featured_listing',10,1),('30 Days Featured Company','company',30,99.00,'SGD','featured_company',20,1),('7 Days Deal Boost','deal',7,29.00,'SGD','featured_deal',15,1);



-- ============================================================
-- Source: install/migration_import_center.sql
-- ============================================================

-- MJ Marketplace Import Center Upgrade
-- Run this on an existing MJ Marketplace Global Pro database before using Admin > Import Center.

CREATE TABLE IF NOT EXISTS mj_company_seed (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  country_id INT NOT NULL,
  state_id BIGINT NULL,
  city_id BIGINT NULL,
  source VARCHAR(80) DEFAULT 'csv',
  source_ref VARCHAR(190) NULL,
  uen VARCHAR(120) NULL,
  entity_name VARCHAR(255) NOT NULL,
  entity_type VARCHAR(160) NULL,
  entity_status VARCHAR(120) NULL,
  registration_date DATE NULL,
  primary_activity TEXT NULL,
  secondary_activity TEXT NULL,
  address_text TEXT NULL,
  postal_code VARCHAR(30) NULL,
  phone VARCHAR(80) NULL,
  email VARCHAR(190) NULL,
  website VARCHAR(255) NULL,
  services TEXT NULL,
  category_text VARCHAR(190) NULL,
  latitude DECIMAL(12,8) NULL,
  longitude DECIMAL(12,8) NULL,
  timezone VARCHAR(80) DEFAULT 'UTC',
  is_claimed TINYINT(1) DEFAULT 0,
  claimed_company_id BIGINT NULL,
  raw_json JSON NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  UNIQUE KEY uniq_seed_country_source (country_id, source, source_ref, uen, entity_name),
  INDEX idx_seed_country(country_id),
  INDEX idx_seed_city(city_id),
  FULLTEXT KEY ft_seed(entity_name,address_text,services,category_text)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_company_seed (fresh installer safe).
CALL mj_safe_add_column('mj_company_seed', 'country_id', '`country_id` INT NOT NULL');
CALL mj_safe_add_column('mj_company_seed', 'state_id', '`state_id` BIGINT NULL');
CALL mj_safe_add_column('mj_company_seed', 'city_id', '`city_id` BIGINT NULL');
CALL mj_safe_add_column('mj_company_seed', 'source', '`source` VARCHAR(80) DEFAULT ''csv''');
CALL mj_safe_add_column('mj_company_seed', 'source_ref', '`source_ref` VARCHAR(190) NULL');
CALL mj_safe_add_column('mj_company_seed', 'uen', '`uen` VARCHAR(120) NULL');
CALL mj_safe_add_column('mj_company_seed', 'entity_name', '`entity_name` VARCHAR(255) NOT NULL');
CALL mj_safe_add_column('mj_company_seed', 'entity_type', '`entity_type` VARCHAR(160) NULL');
CALL mj_safe_add_column('mj_company_seed', 'entity_status', '`entity_status` VARCHAR(120) NULL');
CALL mj_safe_add_column('mj_company_seed', 'registration_date', '`registration_date` DATE NULL');
CALL mj_safe_add_column('mj_company_seed', 'primary_activity', '`primary_activity` TEXT NULL');
CALL mj_safe_add_column('mj_company_seed', 'secondary_activity', '`secondary_activity` TEXT NULL');
CALL mj_safe_add_column('mj_company_seed', 'address_text', '`address_text` TEXT NULL');
CALL mj_safe_add_column('mj_company_seed', 'postal_code', '`postal_code` VARCHAR(30) NULL');
CALL mj_safe_add_column('mj_company_seed', 'phone', '`phone` VARCHAR(80) NULL');
CALL mj_safe_add_column('mj_company_seed', 'email', '`email` VARCHAR(190) NULL');
CALL mj_safe_add_column('mj_company_seed', 'website', '`website` VARCHAR(255) NULL');
CALL mj_safe_add_column('mj_company_seed', 'services', '`services` TEXT NULL');
CALL mj_safe_add_column('mj_company_seed', 'category_text', '`category_text` VARCHAR(190) NULL');
CALL mj_safe_add_column('mj_company_seed', 'latitude', '`latitude` DECIMAL(12,8) NULL');
CALL mj_safe_add_column('mj_company_seed', 'longitude', '`longitude` DECIMAL(12,8) NULL');
CALL mj_safe_add_column('mj_company_seed', 'timezone', '`timezone` VARCHAR(80) DEFAULT ''UTC''');
CALL mj_safe_add_column('mj_company_seed', 'is_claimed', '`is_claimed` TINYINT(1) DEFAULT 0');
CALL mj_safe_add_column('mj_company_seed', 'claimed_company_id', '`claimed_company_id` BIGINT NULL');
CALL mj_safe_add_column('mj_company_seed', 'raw_json', '`raw_json` JSON NULL');
CALL mj_safe_add_column('mj_company_seed', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');
CALL mj_safe_add_column('mj_company_seed', 'updated_at', '`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');


CREATE TABLE IF NOT EXISTS mj_company_claims (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  seed_id BIGINT NULL,
  company_id BIGINT NULL,
  user_id BIGINT NOT NULL,
  claim_name VARCHAR(220) NOT NULL,
  claim_email VARCHAR(190) NULL,
  claim_phone VARCHAR(80) NULL,
  proof_path VARCHAR(255) NULL,
  status ENUM('pending','approved','rejected') DEFAULT 'pending',
  admin_notes TEXT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  INDEX idx_claim_status(status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_company_claims (fresh installer safe).
CALL mj_safe_add_column('mj_company_claims', 'seed_id', '`seed_id` BIGINT NULL');
CALL mj_safe_add_column('mj_company_claims', 'company_id', '`company_id` BIGINT NULL');
CALL mj_safe_add_column('mj_company_claims', 'user_id', '`user_id` BIGINT NOT NULL');
CALL mj_safe_add_column('mj_company_claims', 'claim_name', '`claim_name` VARCHAR(220) NOT NULL');
CALL mj_safe_add_column('mj_company_claims', 'claim_email', '`claim_email` VARCHAR(190) NULL');
CALL mj_safe_add_column('mj_company_claims', 'claim_phone', '`claim_phone` VARCHAR(80) NULL');
CALL mj_safe_add_column('mj_company_claims', 'proof_path', '`proof_path` VARCHAR(255) NULL');
CALL mj_safe_add_column('mj_company_claims', 'status', '`status` ENUM(''pending'',''approved'',''rejected'') DEFAULT ''pending''');
CALL mj_safe_add_column('mj_company_claims', 'admin_notes', '`admin_notes` TEXT NULL');
CALL mj_safe_add_column('mj_company_claims', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');
CALL mj_safe_add_column('mj_company_claims', 'updated_at', '`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');




-- ============================================================
-- Source: install/migration_v2.6_listing_module_pro.sql
-- ============================================================

-- MJ Marketplace v2.6 Listing Module Pro migration
-- Safe to run after backup. If your MySQL version does not support IF NOT EXISTS for columns,
-- the PHP pages also auto-create missing columns using mj_ensure_listing_module_schema().

CALL mj_safe_add_column('mj_listings', 'expires_at', 'expires_at DATETIME NULL');
CALL mj_safe_add_column('mj_listings', 'expired_at', 'expired_at DATETIME NULL');
CALL mj_safe_add_column('mj_listings', 'approved_at', 'approved_at DATETIME NULL');
CALL mj_safe_add_column('mj_listings', 'approved_by', 'approved_by INT NULL');
CALL mj_safe_add_column('mj_listings', 'rejected_at', 'rejected_at DATETIME NULL');
CALL mj_safe_add_column('mj_listings', 'rejected_by', 'rejected_by INT NULL');
CALL mj_safe_add_column('mj_listings', 'reject_reason', 'reject_reason TEXT NULL');
CALL mj_safe_add_column('mj_listings', 'inactive_at', 'inactive_at DATETIME NULL');
CALL mj_safe_add_column('mj_listings', 'inactive_by', 'inactive_by INT NULL');
CALL mj_safe_add_column('mj_listings', 'reactivated_at', 'reactivated_at DATETIME NULL');
CALL mj_safe_add_column('mj_listings', 'deleted_at', 'deleted_at DATETIME NULL');
CALL mj_safe_add_column('mj_listings', 'deleted_by', 'deleted_by INT NULL');
CALL mj_safe_add_column('mj_listings', 'is_deleted', 'is_deleted TINYINT(1) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_listings', 'is_featured', 'is_featured TINYINT(1) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_listings', 'featured_until', 'featured_until DATETIME NULL');
CALL mj_safe_add_column('mj_listings', 'featured_priority', 'featured_priority INT NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_listings', 'is_sponsored', 'is_sponsored TINYINT(1) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_listings', 'sponsored_until', 'sponsored_until DATETIME NULL');
CALL mj_safe_add_column('mj_listings', 'sponsored_priority', 'sponsored_priority INT NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_listings', 'sponsor_keywords', 'sponsor_keywords TEXT NULL');
CALL mj_safe_add_column('mj_listings', 'search_boost_keywords', 'search_boost_keywords TEXT NULL');
CALL mj_safe_add_column('mj_listings', 'sort_priority', 'sort_priority INT NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_listings', 'seo_title', 'seo_title VARCHAR(255) NULL');
CALL mj_safe_add_column('mj_listings', 'seo_description', 'seo_description TEXT NULL');
CALL mj_safe_add_column('mj_listings', 'admin_note', 'admin_note TEXT NULL');
CALL mj_safe_add_column('mj_listings', 'listing_quality_score', 'listing_quality_score INT NOT NULL DEFAULT 0');

CREATE TABLE IF NOT EXISTS mj_listing_revisions (
  id INT AUTO_INCREMENT PRIMARY KEY,
  listing_id INT NOT NULL,
  user_id INT NULL,
  old_data_json LONGTEXT NULL,
  new_data_json LONGTEXT NULL,
  status VARCHAR(40) NOT NULL DEFAULT 'pending',
  admin_id INT NULL,
  admin_note TEXT NULL,
  reject_reason TEXT NULL,
  submitted_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  reviewed_at DATETIME NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX(listing_id), INDEX(status), INDEX(user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_listing_logs (
  id INT AUTO_INCREMENT PRIMARY KEY,
  listing_id INT NOT NULL,
  user_id INT NULL,
  admin_id INT NULL,
  action VARCHAR(100) NOT NULL,
  old_data_json LONGTEXT NULL,
  new_data_json LONGTEXT NULL,
  ip_address VARCHAR(80) NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX(listing_id), INDEX(action), INDEX(created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_listing_expiry_logs (
  id INT AUTO_INCREMENT PRIMARY KEY,
  listing_id INT NOT NULL,
  old_expires_at DATETIME NULL,
  new_expires_at DATETIME NULL,
  action VARCHAR(80) NOT NULL,
  admin_id INT NULL,
  user_id INT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX(listing_id), INDEX(created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

INSERT INTO mj_settings(setting_key,setting_value) VALUES
('listing_default_expiry_days','30'),
('listing_approval_required','1'),
('show_sold_listings','1'),
('user_renew_requires_approval','1'),
('listing_allow_user_delete','1'),
('listing_allow_user_inactive','1')
ON DUPLICATE KEY UPDATE setting_value=VALUES(setting_value);



-- ============================================================
-- Source: install/migration_v2.6.3_listing_module_advanced.sql
-- ============================================================

-- MJ Marketplace v2.6.3 Listing Module Advanced
-- Run after v2.6.2. Safe to run multiple times where supported by MariaDB 10.3+ for CREATE TABLE.

CALL mj_safe_add_column('mj_listings', 'views', 'views INT NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_listings', 'unique_views', 'unique_views INT NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_listings', 'engagement_count', 'engagement_count INT NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_listings', 'offer_count', 'offer_count INT NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_listings', 'chat_count', 'chat_count INT NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_listings', 'save_count', 'save_count INT NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_listings', 'share_count', 'share_count INT NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_listings', 'phone_click_count', 'phone_click_count INT NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_listings', 'whatsapp_click_count', 'whatsapp_click_count INT NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_listings', 'last_viewed_at', 'last_viewed_at DATETIME NULL');
CALL mj_safe_add_column('mj_listings', 'last_engaged_at', 'last_engaged_at DATETIME NULL');
CALL mj_safe_add_column('mj_listings', 'old_price', 'old_price DECIMAL(12,2) NULL');
CALL mj_safe_add_column('mj_listings', 'min_offer_price', 'min_offer_price DECIMAL(12,2) NULL');
CALL mj_safe_add_column('mj_listings', 'is_negotiable', 'is_negotiable TINYINT(1) NOT NULL DEFAULT 1');
CALL mj_safe_add_column('mj_listings', 'correction_reason', 'correction_reason TEXT NULL');
CALL mj_safe_add_column('mj_listings', 'availability_status', 'availability_status VARCHAR(80) NULL');
CALL mj_safe_add_column('mj_listings', 'delivery_options', 'delivery_options TEXT NULL');
CALL mj_safe_add_column('mj_listings', 'contact_options', 'contact_options TEXT NULL');
CALL mj_safe_add_column('mj_listings', 'short_code', 'short_code VARCHAR(80) NULL');

CREATE TABLE IF NOT EXISTS mj_listing_renewal_requests (
  id INT AUTO_INCREMENT PRIMARY KEY,
  listing_id INT NOT NULL,
  user_id INT NOT NULL,
  current_expires_at DATETIME NULL,
  requested_days INT NOT NULL DEFAULT 30,
  requested_expires_at DATETIME NULL,
  status VARCHAR(50) NOT NULL DEFAULT 'pending',
  payment_required TINYINT(1) NOT NULL DEFAULT 0,
  payment_amount DECIMAL(12,2) NULL,
  payment_currency VARCHAR(10) NULL,
  payment_method VARCHAR(80) NULL,
  payment_status VARCHAR(50) NULL,
  payment_proof_id INT NULL,
  admin_id INT NULL,
  admin_note TEXT NULL,
  reject_reason TEXT NULL,
  requested_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  reviewed_at DATETIME NULL,
  INDEX(listing_id), INDEX(user_id), INDEX(status), INDEX(payment_status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_listing_events (
  id INT AUTO_INCREMENT PRIMARY KEY,
  listing_id INT NOT NULL,
  user_id INT NULL,
  event_type VARCHAR(80) NOT NULL,
  event_value VARCHAR(191) NULL,
  ip_address VARCHAR(80) NULL,
  user_agent TEXT NULL,
  session_key VARCHAR(191) NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX(listing_id), INDEX(event_type), INDEX(created_at), INDEX(session_key)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_listing_reports (
  id INT AUTO_INCREMENT PRIMARY KEY,
  listing_id INT NOT NULL,
  user_id INT NULL,
  reason VARCHAR(120) NOT NULL,
  message TEXT NULL,
  status VARCHAR(40) NOT NULL DEFAULT 'new',
  admin_id INT NULL,
  admin_note TEXT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  reviewed_at DATETIME NULL,
  INDEX(listing_id), INDEX(status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_listing_questions (
  id INT AUTO_INCREMENT PRIMARY KEY,
  listing_id INT NOT NULL,
  user_id INT NULL,
  question TEXT NOT NULL,
  answer TEXT NULL,
  status VARCHAR(40) NOT NULL DEFAULT 'pending',
  answered_by INT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  answered_at DATETIME NULL,
  INDEX(listing_id), INDEX(status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_listing_faqs (
  id INT AUTO_INCREMENT PRIMARY KEY,
  listing_id INT NOT NULL,
  question VARCHAR(255) NOT NULL,
  answer TEXT NOT NULL,
  sort_order INT NOT NULL DEFAULT 0,
  is_active TINYINT(1) NOT NULL DEFAULT 1,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX(listing_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_payment_methods (
  id INT AUTO_INCREMENT PRIMARY KEY,
  method_key VARCHAR(80) NOT NULL UNIQUE,
  method_name VARCHAR(120) NOT NULL,
  is_enabled TINYINT(1) NOT NULL DEFAULT 0,
  details_json LONGTEXT NULL,
  instructions TEXT NULL,
  qr_image VARCHAR(500) NULL,
  country_id INT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_payment_proofs (
  id INT AUTO_INCREMENT PRIMARY KEY,
  user_id INT NULL,
  related_type VARCHAR(80) NULL,
  related_id INT NULL,
  payment_method VARCHAR(80) NULL,
  amount DECIMAL(12,2) NULL,
  currency VARCHAR(10) NULL,
  proof_file VARCHAR(500) NULL,
  status VARCHAR(50) NOT NULL DEFAULT 'uploaded',
  admin_id INT NULL,
  admin_note TEXT NULL,
  uploaded_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  reviewed_at DATETIME NULL,
  INDEX(user_id), INDEX(related_type, related_id), INDEX(status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_notifications (
  id INT AUTO_INCREMENT PRIMARY KEY,
  user_id INT NULL,
  admin_id INT NULL,
  type VARCHAR(80) NOT NULL,
  title VARCHAR(255) NOT NULL,
  message TEXT NULL,
  priority VARCHAR(40) NOT NULL DEFAULT 'normal',
  action_url VARCHAR(500) NULL,
  is_read TINYINT(1) NOT NULL DEFAULT 0,
  is_popup_shown TINYINT(1) NOT NULL DEFAULT 0,
  popup_shown_at DATETIME NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX(user_id), INDEX(admin_id), INDEX(is_read), INDEX(is_popup_shown), INDEX(created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_notifications (fresh installer safe).
CALL mj_safe_add_column('mj_notifications', 'user_id', '`user_id` INT NULL');
CALL mj_safe_add_column('mj_notifications', 'admin_id', '`admin_id` INT NULL');
CALL mj_safe_add_column('mj_notifications', 'type', '`type` VARCHAR(80) NOT NULL');
CALL mj_safe_add_column('mj_notifications', 'title', '`title` VARCHAR(255) NOT NULL');
CALL mj_safe_add_column('mj_notifications', 'message', '`message` TEXT NULL');
CALL mj_safe_add_column('mj_notifications', 'priority', '`priority` VARCHAR(40) NOT NULL DEFAULT ''normal''');
CALL mj_safe_add_column('mj_notifications', 'action_url', '`action_url` VARCHAR(500) NULL');
CALL mj_safe_add_column('mj_notifications', 'is_read', '`is_read` TINYINT(1) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_notifications', 'is_popup_shown', '`is_popup_shown` TINYINT(1) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_notifications', 'popup_shown_at', '`popup_shown_at` DATETIME NULL');
CALL mj_safe_add_column('mj_notifications', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');


CREATE TABLE IF NOT EXISTS mj_email_queue (
  id INT AUTO_INCREMENT PRIMARY KEY,
  user_id INT NULL,
  to_email VARCHAR(191) NOT NULL,
  subject VARCHAR(255) NOT NULL,
  body_html LONGTEXT NULL,
  body_text LONGTEXT NULL,
  template_key VARCHAR(100) NULL,
  status VARCHAR(40) NOT NULL DEFAULT 'pending',
  priority VARCHAR(40) NOT NULL DEFAULT 'normal',
  scheduled_at DATETIME NULL,
  sent_at DATETIME NULL,
  failed_at DATETIME NULL,
  error_message TEXT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX(user_id), INDEX(status), INDEX(template_key)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_notification_settings (
  id INT AUTO_INCREMENT PRIMARY KEY,
  setting_key VARCHAR(120) NOT NULL UNIQUE,
  setting_value TEXT NULL,
  updated_at DATETIME NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_user_notification_preferences (
  id INT AUTO_INCREMENT PRIMARY KEY,
  user_id INT NOT NULL,
  notification_type VARCHAR(100) NOT NULL,
  in_app_enabled TINYINT(1) NOT NULL DEFAULT 1,
  email_enabled TINYINT(1) NOT NULL DEFAULT 1,
  popup_enabled TINYINT(1) NOT NULL DEFAULT 1,
  UNIQUE KEY uniq_user_type(user_id,notification_type)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;



-- ============================================================
-- Source: install/migration_v2.6.4_company_module_advanced.sql
-- ============================================================

-- MJ Marketplace v2.6.4 Company Module Advanced Upgrade
-- Safe additive migration for existing v2.6.3 databases.
-- Adds company editing/review support, quality scores, sponsor flags and timeline.

CALL mj_safe_add_column('mj_companies', 'admin_note', 'admin_note TEXT NULL AFTER views');
CALL mj_safe_add_column('mj_companies', 'rejection_reason', 'rejection_reason TEXT NULL AFTER admin_note');
CALL mj_safe_add_column('mj_companies', 'is_sponsored', 'is_sponsored TINYINT(1) NOT NULL DEFAULT 0 AFTER is_featured');
CALL mj_safe_add_column('mj_companies', 'sponsored_until', 'sponsored_until DATETIME NULL AFTER is_sponsored');
CALL mj_safe_add_column('mj_companies', 'verification_status', 'verification_status VARCHAR(40) NOT NULL DEFAULT ''not_verified'' AFTER is_verified');
CALL mj_safe_add_column('mj_companies', 'verification_until', 'verification_until DATE NULL AFTER verification_status');
CALL mj_safe_add_column('mj_companies', 'trust_score', 'trust_score INT NOT NULL DEFAULT 0 AFTER rating_count');
CALL mj_safe_add_column('mj_companies', 'profile_score', 'profile_score INT NOT NULL DEFAULT 0 AFTER trust_score');
CALL mj_safe_add_column('mj_companies', 'last_reviewed_at', 'last_reviewed_at DATETIME NULL AFTER updated_at');
CALL mj_safe_add_column('mj_companies', 'last_reviewed_by', 'last_reviewed_by INT NULL AFTER last_reviewed_at');
CALL mj_safe_add_column('mj_companies', 'submitted_at', 'submitted_at DATETIME NULL AFTER last_reviewed_by');

CREATE TABLE IF NOT EXISTS mj_company_timeline (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  company_id BIGINT NOT NULL,
  actor_type VARCHAR(30) NOT NULL DEFAULT 'system',
  actor_id BIGINT NULL,
  action VARCHAR(120) NOT NULL,
  note TEXT NULL,
  old_status VARCHAR(40) NULL,
  new_status VARCHAR(40) NULL,
  ip_address VARCHAR(80) NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_company_timeline_company(company_id),
  INDEX idx_company_timeline_action(action)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_company_timeline (fresh installer safe).
CALL mj_safe_add_column('mj_company_timeline', 'company_id', '`company_id` BIGINT NOT NULL');
CALL mj_safe_add_column('mj_company_timeline', 'actor_type', '`actor_type` VARCHAR(30) NOT NULL DEFAULT ''system''');
CALL mj_safe_add_column('mj_company_timeline', 'actor_id', '`actor_id` BIGINT NULL');
CALL mj_safe_add_column('mj_company_timeline', 'action', '`action` VARCHAR(120) NOT NULL');
CALL mj_safe_add_column('mj_company_timeline', 'note', '`note` TEXT NULL');
CALL mj_safe_add_column('mj_company_timeline', 'old_status', '`old_status` VARCHAR(40) NULL');
CALL mj_safe_add_column('mj_company_timeline', 'new_status', '`new_status` VARCHAR(40) NULL');
CALL mj_safe_add_column('mj_company_timeline', 'ip_address', '`ip_address` VARCHAR(80) NULL');
CALL mj_safe_add_column('mj_company_timeline', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');




-- ============================================================
-- Source: install/migration_v2.6.5_listing_company_trust.sql
-- ============================================================

-- MJ Marketplace v2.6.5 Listing + Company Trust Add-on
-- Safe additive migration for existing v2.6.3/v2.6.4 databases.
-- Adds save tables and company contact click tracking used by listing detail company box.

CREATE TABLE IF NOT EXISTS mj_saved_listings (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  listing_id BIGINT NOT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  UNIQUE KEY uniq_saved_listing(user_id,listing_id),
  KEY idx_listing_id(listing_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_saved_listings (fresh installer safe).
CALL mj_safe_add_column('mj_saved_listings', 'user_id', '`user_id` BIGINT NOT NULL');
CALL mj_safe_add_column('mj_saved_listings', 'listing_id', '`listing_id` BIGINT NOT NULL');
CALL mj_safe_add_column('mj_saved_listings', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');


CREATE TABLE IF NOT EXISTS mj_saved_companies (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  company_id BIGINT NOT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  UNIQUE KEY uniq_saved_company(user_id,company_id),
  KEY idx_company_id(company_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_saved_companies (fresh installer safe).
CALL mj_safe_add_column('mj_saved_companies', 'user_id', '`user_id` BIGINT NOT NULL');
CALL mj_safe_add_column('mj_saved_companies', 'company_id', '`company_id` BIGINT NOT NULL');
CALL mj_safe_add_column('mj_saved_companies', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');


CREATE TABLE IF NOT EXISTS mj_company_events (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  company_id BIGINT NOT NULL,
  user_id BIGINT NULL,
  event_type VARCHAR(80) NOT NULL,
  event_value VARCHAR(191) NULL,
  ip_address VARCHAR(80) NULL,
  user_agent TEXT NULL,
  session_key VARCHAR(191) NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  KEY idx_company_id(company_id),
  KEY idx_event_type(event_type),
  KEY idx_created_at(created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CALL mj_safe_add_column('mj_companies', 'phone_click_count', 'phone_click_count BIGINT NOT NULL DEFAULT 0 AFTER views');
CALL mj_safe_add_column('mj_companies', 'whatsapp_click_count', 'whatsapp_click_count BIGINT NOT NULL DEFAULT 0 AFTER phone_click_count');
CALL mj_safe_add_column('mj_companies', 'email_click_count', 'email_click_count BIGINT NOT NULL DEFAULT 0 AFTER whatsapp_click_count');
CALL mj_safe_add_column('mj_companies', 'website_click_count', 'website_click_count BIGINT NOT NULL DEFAULT 0 AFTER email_click_count');



-- ============================================================
-- Source: install/migration_v2.6.7_realtime_chat_notifications.sql
-- ============================================================

-- MJ Marketplace v2.6.7 Real-Time Chat + Overlay Notification
-- Safe additive migration for existing v2.6.3/v2.6.6 databases.

CALL mj_safe_add_column('mj_chats', 'buyer_archived', 'buyer_archived TINYINT(1) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_chats', 'seller_archived', 'seller_archived TINYINT(1) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_chats', 'buyer_pinned', 'buyer_pinned TINYINT(1) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_chats', 'seller_pinned', 'seller_pinned TINYINT(1) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_chats', 'buyer_muted_until', 'buyer_muted_until DATETIME NULL');
CALL mj_safe_add_column('mj_chats', 'seller_muted_until', 'seller_muted_until DATETIME NULL');
CALL mj_safe_add_column('mj_chats', 'buyer_mark_unread', 'buyer_mark_unread TINYINT(1) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_chats', 'seller_mark_unread', 'seller_mark_unread TINYINT(1) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_chats', 'reported_at', 'reported_at DATETIME NULL');
CALL mj_safe_add_column('mj_chats', 'last_read_buyer_at', 'last_read_buyer_at DATETIME NULL');
CALL mj_safe_add_column('mj_chats', 'last_read_seller_at', 'last_read_seller_at DATETIME NULL');

CALL mj_safe_add_column('mj_chat_messages', 'read_at', 'read_at DATETIME NULL AFTER is_read');
CALL mj_safe_add_column('mj_chat_messages', 'is_deleted', 'is_deleted TINYINT(1) NOT NULL DEFAULT 0 AFTER read_at');



-- ============================================================
-- Source: install/migration_v2.6.9_quote_request_page.sql
-- ============================================================

-- MJ Marketplace v2.6.9 Quote Request Page
-- Safe additive migration for quotation request contact fields.

CREATE TABLE IF NOT EXISTS mj_quote_requests (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT NULL,
  company_id BIGINT NULL,
  service_id BIGINT NULL,
  listing_id BIGINT NULL,
  country_id INT NULL,
  city_id BIGINT NULL,
  title VARCHAR(255) NULL,
  description TEXT NULL,
  urgency VARCHAR(80) NULL,
  requester_name VARCHAR(180) NULL,
  requester_email VARCHAR(190) NULL,
  requester_phone VARCHAR(80) NULL,
  budget VARCHAR(120) NULL,
  preferred_date DATE NULL,
  preferred_contact VARCHAR(40) NULL,
  status VARCHAR(60) DEFAULT 'new',
  ip_address VARCHAR(80) NULL,
  user_agent VARCHAR(255) NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX(company_id,status),
  INDEX(user_id),
  INDEX(listing_id),
  INDEX(status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_quote_requests (fresh installer safe).
CALL mj_safe_add_column('mj_quote_requests', 'user_id', '`user_id` BIGINT NULL');
CALL mj_safe_add_column('mj_quote_requests', 'company_id', '`company_id` BIGINT NULL');
CALL mj_safe_add_column('mj_quote_requests', 'service_id', '`service_id` BIGINT NULL');
CALL mj_safe_add_column('mj_quote_requests', 'listing_id', '`listing_id` BIGINT NULL');
CALL mj_safe_add_column('mj_quote_requests', 'country_id', '`country_id` INT NULL');
CALL mj_safe_add_column('mj_quote_requests', 'city_id', '`city_id` BIGINT NULL');
CALL mj_safe_add_column('mj_quote_requests', 'title', '`title` VARCHAR(255) NULL');
CALL mj_safe_add_column('mj_quote_requests', 'description', '`description` TEXT NULL');
CALL mj_safe_add_column('mj_quote_requests', 'urgency', '`urgency` VARCHAR(80) NULL');
CALL mj_safe_add_column('mj_quote_requests', 'requester_name', '`requester_name` VARCHAR(180) NULL');
CALL mj_safe_add_column('mj_quote_requests', 'requester_email', '`requester_email` VARCHAR(190) NULL');
CALL mj_safe_add_column('mj_quote_requests', 'requester_phone', '`requester_phone` VARCHAR(80) NULL');
CALL mj_safe_add_column('mj_quote_requests', 'budget', '`budget` VARCHAR(120) NULL');
CALL mj_safe_add_column('mj_quote_requests', 'preferred_date', '`preferred_date` DATE NULL');
CALL mj_safe_add_column('mj_quote_requests', 'preferred_contact', '`preferred_contact` VARCHAR(40) NULL');
CALL mj_safe_add_column('mj_quote_requests', 'status', '`status` VARCHAR(60) DEFAULT ''new''');
CALL mj_safe_add_column('mj_quote_requests', 'ip_address', '`ip_address` VARCHAR(80) NULL');
CALL mj_safe_add_column('mj_quote_requests', 'user_agent', '`user_agent` VARCHAR(255) NULL');
CALL mj_safe_add_column('mj_quote_requests', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');


CALL mj_safe_add_column('mj_quote_requests', 'listing_id', 'listing_id BIGINT NULL AFTER service_id');
CALL mj_safe_add_column('mj_quote_requests', 'requester_name', 'requester_name VARCHAR(180) NULL AFTER urgency');
CALL mj_safe_add_column('mj_quote_requests', 'requester_email', 'requester_email VARCHAR(190) NULL AFTER requester_name');
CALL mj_safe_add_column('mj_quote_requests', 'requester_phone', 'requester_phone VARCHAR(80) NULL AFTER requester_email');
CALL mj_safe_add_column('mj_quote_requests', 'budget', 'budget VARCHAR(120) NULL AFTER requester_phone');
CALL mj_safe_add_column('mj_quote_requests', 'preferred_date', 'preferred_date DATE NULL AFTER budget');
CALL mj_safe_add_column('mj_quote_requests', 'preferred_contact', 'preferred_contact VARCHAR(40) NULL AFTER preferred_date');
CALL mj_safe_add_column('mj_quote_requests', 'ip_address', 'ip_address VARCHAR(80) NULL AFTER status');
CALL mj_safe_add_column('mj_quote_requests', 'user_agent', 'user_agent VARCHAR(255) NULL AFTER ip_address');



-- ============================================================
-- Source: install/migration_v2.7.0_quote_management.sql
-- ============================================================

-- MJ Marketplace v2.7.0 Quote Management System
-- Safe additive migration for existing databases.

CREATE TABLE IF NOT EXISTS mj_quote_requests (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT NULL,
  company_id BIGINT NULL,
  service_id BIGINT NULL,
  listing_id BIGINT NULL,
  country_id INT NULL,
  city_id BIGINT NULL,
  title VARCHAR(255) NULL,
  description TEXT NULL,
  urgency VARCHAR(80) NULL,
  requester_name VARCHAR(180) NULL,
  requester_email VARCHAR(190) NULL,
  requester_phone VARCHAR(80) NULL,
  budget VARCHAR(120) NULL,
  preferred_date DATE NULL,
  preferred_contact VARCHAR(40) NULL,
  status VARCHAR(60) DEFAULT 'new',
  quoted_amount DECIMAL(12,2) NULL,
  currency_code VARCHAR(10) NULL,
  quote_message TEXT NULL,
  valid_until DATE NULL,
  lead_score INT NOT NULL DEFAULT 0,
  assigned_to BIGINT NULL,
  internal_note TEXT NULL,
  source VARCHAR(80) NULL,
  ip_address VARCHAR(80) NULL,
  user_agent VARCHAR(255) NULL,
  replied_at DATETIME NULL,
  accepted_at DATETIME NULL,
  closed_at DATETIME NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NULL,
  INDEX(company_id,status),
  INDEX(user_id),
  INDEX(listing_id),
  INDEX(status),
  INDEX(created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_quote_requests (fresh installer safe).
CALL mj_safe_add_column('mj_quote_requests', 'user_id', '`user_id` BIGINT NULL');
CALL mj_safe_add_column('mj_quote_requests', 'company_id', '`company_id` BIGINT NULL');
CALL mj_safe_add_column('mj_quote_requests', 'service_id', '`service_id` BIGINT NULL');
CALL mj_safe_add_column('mj_quote_requests', 'listing_id', '`listing_id` BIGINT NULL');
CALL mj_safe_add_column('mj_quote_requests', 'country_id', '`country_id` INT NULL');
CALL mj_safe_add_column('mj_quote_requests', 'city_id', '`city_id` BIGINT NULL');
CALL mj_safe_add_column('mj_quote_requests', 'title', '`title` VARCHAR(255) NULL');
CALL mj_safe_add_column('mj_quote_requests', 'description', '`description` TEXT NULL');
CALL mj_safe_add_column('mj_quote_requests', 'urgency', '`urgency` VARCHAR(80) NULL');
CALL mj_safe_add_column('mj_quote_requests', 'requester_name', '`requester_name` VARCHAR(180) NULL');
CALL mj_safe_add_column('mj_quote_requests', 'requester_email', '`requester_email` VARCHAR(190) NULL');
CALL mj_safe_add_column('mj_quote_requests', 'requester_phone', '`requester_phone` VARCHAR(80) NULL');
CALL mj_safe_add_column('mj_quote_requests', 'budget', '`budget` VARCHAR(120) NULL');
CALL mj_safe_add_column('mj_quote_requests', 'preferred_date', '`preferred_date` DATE NULL');
CALL mj_safe_add_column('mj_quote_requests', 'preferred_contact', '`preferred_contact` VARCHAR(40) NULL');
CALL mj_safe_add_column('mj_quote_requests', 'status', '`status` VARCHAR(60) DEFAULT ''new''');
CALL mj_safe_add_column('mj_quote_requests', 'quoted_amount', '`quoted_amount` DECIMAL(12,2) NULL');
CALL mj_safe_add_column('mj_quote_requests', 'currency_code', '`currency_code` VARCHAR(10) NULL');
CALL mj_safe_add_column('mj_quote_requests', 'quote_message', '`quote_message` TEXT NULL');
CALL mj_safe_add_column('mj_quote_requests', 'valid_until', '`valid_until` DATE NULL');
CALL mj_safe_add_column('mj_quote_requests', 'lead_score', '`lead_score` INT NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_quote_requests', 'assigned_to', '`assigned_to` BIGINT NULL');
CALL mj_safe_add_column('mj_quote_requests', 'internal_note', '`internal_note` TEXT NULL');
CALL mj_safe_add_column('mj_quote_requests', 'source', '`source` VARCHAR(80) NULL');
CALL mj_safe_add_column('mj_quote_requests', 'ip_address', '`ip_address` VARCHAR(80) NULL');
CALL mj_safe_add_column('mj_quote_requests', 'user_agent', '`user_agent` VARCHAR(255) NULL');
CALL mj_safe_add_column('mj_quote_requests', 'replied_at', '`replied_at` DATETIME NULL');
CALL mj_safe_add_column('mj_quote_requests', 'accepted_at', '`accepted_at` DATETIME NULL');
CALL mj_safe_add_column('mj_quote_requests', 'closed_at', '`closed_at` DATETIME NULL');
CALL mj_safe_add_column('mj_quote_requests', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');
CALL mj_safe_add_column('mj_quote_requests', 'updated_at', '`updated_at` DATETIME NULL');


CALL mj_safe_add_column('mj_quote_requests', 'quoted_amount', 'quoted_amount DECIMAL(12,2) NULL AFTER status');
CALL mj_safe_add_column('mj_quote_requests', 'currency_code', 'currency_code VARCHAR(10) NULL AFTER quoted_amount');
CALL mj_safe_add_column('mj_quote_requests', 'quote_message', 'quote_message TEXT NULL AFTER currency_code');
CALL mj_safe_add_column('mj_quote_requests', 'valid_until', 'valid_until DATE NULL AFTER quote_message');
CALL mj_safe_add_column('mj_quote_requests', 'lead_score', 'lead_score INT NOT NULL DEFAULT 0 AFTER valid_until');
CALL mj_safe_add_column('mj_quote_requests', 'assigned_to', 'assigned_to BIGINT NULL AFTER lead_score');
CALL mj_safe_add_column('mj_quote_requests', 'internal_note', 'internal_note TEXT NULL AFTER assigned_to');
CALL mj_safe_add_column('mj_quote_requests', 'source', 'source VARCHAR(80) NULL AFTER internal_note');
CALL mj_safe_add_column('mj_quote_requests', 'replied_at', 'replied_at DATETIME NULL AFTER user_agent');
CALL mj_safe_add_column('mj_quote_requests', 'accepted_at', 'accepted_at DATETIME NULL AFTER replied_at');
CALL mj_safe_add_column('mj_quote_requests', 'closed_at', 'closed_at DATETIME NULL AFTER accepted_at');
CALL mj_safe_add_column('mj_quote_requests', 'updated_at', 'updated_at DATETIME NULL AFTER created_at');

CREATE TABLE IF NOT EXISTS mj_quote_replies (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  quote_id BIGINT NOT NULL,
  sender_type VARCHAR(30) NOT NULL DEFAULT 'company',
  sender_user_id BIGINT NULL,
  message TEXT NOT NULL,
  amount DECIMAL(12,2) NULL,
  currency_code VARCHAR(10) NULL,
  valid_until DATE NULL,
  attachment_path VARCHAR(255) NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX(quote_id),
  INDEX(sender_user_id),
  INDEX(created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_quote_timeline (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  quote_id BIGINT NOT NULL,
  actor_type VARCHAR(30) NOT NULL DEFAULT 'system',
  actor_id BIGINT NULL,
  action VARCHAR(80) NOT NULL,
  note TEXT NULL,
  old_status VARCHAR(60) NULL,
  new_status VARCHAR(60) NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX(quote_id),
  INDEX(action),
  INDEX(created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_quote_attachments (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  quote_id BIGINT NOT NULL,
  user_id BIGINT NULL,
  file_path VARCHAR(255) NOT NULL,
  original_name VARCHAR(255) NULL,
  mime_type VARCHAR(120) NULL,
  file_size BIGINT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX(quote_id),
  INDEX(user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;


-- mj_notifications quote compatibility
-- Some older installs have mj_notifications(title, body, url) only.
-- The quote module now detects the available columns automatically, so these columns are optional.
-- Add them only if your MySQL supports ADD COLUMN.
-- ALTER TABLE mj_notifications ADD COLUMN type VARCHAR(80) NULL;
-- ALTER TABLE mj_notifications ADD COLUMN message TEXT NULL;
-- ALTER TABLE mj_notifications ADD COLUMN priority VARCHAR(40) NOT NULL DEFAULT 'normal';
-- ALTER TABLE mj_notifications ADD COLUMN action_url VARCHAR(500) NULL;



-- ============================================================
-- Source: install/migration_v2.7.4_offer_management.sql
-- ============================================================

-- MJ Marketplace v2.7.4 Offer Management Upgrade
-- Safe additive migration for existing v2.7.x databases.

CREATE TABLE IF NOT EXISTS mj_offers (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  listing_id BIGINT NOT NULL,
  buyer_id BIGINT NOT NULL,
  seller_id BIGINT NOT NULL,
  amount DECIMAL(12,2) NOT NULL,
  currency_code VARCHAR(8) DEFAULT 'USD',
  status ENUM('pending','accepted','rejected','countered','cancelled','expired') DEFAULT 'pending',
  expires_at DATETIME NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  INDEX(listing_id),
  INDEX(buyer_id),
  INDEX(seller_id),
  INDEX(status),
  INDEX(expires_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_offers (fresh installer safe).
CALL mj_safe_add_column('mj_offers', 'listing_id', '`listing_id` BIGINT NOT NULL');
CALL mj_safe_add_column('mj_offers', 'buyer_id', '`buyer_id` BIGINT NOT NULL');
CALL mj_safe_add_column('mj_offers', 'seller_id', '`seller_id` BIGINT NOT NULL');
CALL mj_safe_add_column('mj_offers', 'amount', '`amount` DECIMAL(12,2) NOT NULL');
CALL mj_safe_add_column('mj_offers', 'currency_code', '`currency_code` VARCHAR(8) DEFAULT ''USD''');
CALL mj_safe_add_column('mj_offers', 'status', '`status` ENUM(''pending'',''accepted'',''rejected'',''countered'',''cancelled'',''expired'') DEFAULT ''pending''');
CALL mj_safe_add_column('mj_offers', 'expires_at', '`expires_at` DATETIME NULL');
CALL mj_safe_add_column('mj_offers', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');
CALL mj_safe_add_column('mj_offers', 'updated_at', '`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');


CALL mj_safe_add_column('mj_offers', 'message', 'message TEXT NULL');
CALL mj_safe_add_column('mj_offers', 'counter_amount', 'counter_amount DECIMAL(12,2) NULL');
CALL mj_safe_add_column('mj_offers', 'counter_by', 'counter_by BIGINT NULL');
CALL mj_safe_add_column('mj_offers', 'accepted_at', 'accepted_at DATETIME NULL');
CALL mj_safe_add_column('mj_offers', 'rejected_at', 'rejected_at DATETIME NULL');
CALL mj_safe_add_column('mj_offers', 'cancelled_at', 'cancelled_at DATETIME NULL');
CALL mj_safe_add_column('mj_offers', 'last_action_by', 'last_action_by BIGINT NULL');
CALL mj_safe_add_column('mj_offers', 'last_action_at', 'last_action_at DATETIME NULL');

CREATE TABLE IF NOT EXISTS mj_offer_timeline (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  offer_id BIGINT NOT NULL,
  actor_id BIGINT NULL,
  action VARCHAR(80) NOT NULL,
  note TEXT NULL,
  old_status VARCHAR(40) NULL,
  new_status VARCHAR(40) NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX(offer_id),
  INDEX(action),
  INDEX(created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;



-- ============================================================
-- Source: install/migration_v2.7.5_order_management.sql
-- ============================================================

-- MJ Marketplace v2.7.5 Order Management System
-- Safe additive migration. Online payment OFF by default.

CREATE TABLE IF NOT EXISTS mj_orders (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  order_number VARCHAR(80) NULL,
  listing_id BIGINT NULL,
  company_id BIGINT NULL,
  offer_id BIGINT NULL,
  quote_id BIGINT NULL,
  source_type VARCHAR(40) NULL,
  source_id BIGINT NULL,
  buyer_id BIGINT NOT NULL,
  seller_id BIGINT NOT NULL,
  country_id INT NULL,
  amount DECIMAL(12,2) NOT NULL DEFAULT 0,
  tax_amount DECIMAL(12,2) NOT NULL DEFAULT 0,
  total_amount DECIMAL(12,2) NOT NULL DEFAULT 0,
  currency_code VARCHAR(8) DEFAULT 'USD',
  status VARCHAR(40) DEFAULT 'pending_payment',
  payment_status VARCHAR(40) DEFAULT 'payment_disabled',
  payment_method VARCHAR(80) NULL,
  payment_reference VARCHAR(160) NULL,
  payment_proof_path VARCHAR(255) NULL,
  payment_proof_note TEXT NULL,
  payment_submitted_at DATETIME NULL,
  payment_verified_at DATETIME NULL,
  payment_verified_by BIGINT NULL,
  payment_rejected_at DATETIME NULL,
  payment_rejection_reason TEXT NULL,
  delivery_method VARCHAR(80) NULL,
  buyer_note TEXT NULL,
  seller_note TEXT NULL,
  admin_note TEXT NULL,
  completed_at DATETIME NULL,
  cancelled_at DATETIME NULL,
  disputed_at DATETIME NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  UNIQUE KEY uniq_order_number(order_number),
  INDEX(buyer_id), INDEX(seller_id), INDEX(status), INDEX(payment_status), INDEX(source_type,source_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_orders (fresh installer safe).
CALL mj_safe_add_column('mj_orders', 'order_number', '`order_number` VARCHAR(80) NULL');
CALL mj_safe_add_column('mj_orders', 'listing_id', '`listing_id` BIGINT NULL');
CALL mj_safe_add_column('mj_orders', 'company_id', '`company_id` BIGINT NULL');
CALL mj_safe_add_column('mj_orders', 'offer_id', '`offer_id` BIGINT NULL');
CALL mj_safe_add_column('mj_orders', 'quote_id', '`quote_id` BIGINT NULL');
CALL mj_safe_add_column('mj_orders', 'source_type', '`source_type` VARCHAR(40) NULL');
CALL mj_safe_add_column('mj_orders', 'source_id', '`source_id` BIGINT NULL');
CALL mj_safe_add_column('mj_orders', 'buyer_id', '`buyer_id` BIGINT NOT NULL');
CALL mj_safe_add_column('mj_orders', 'seller_id', '`seller_id` BIGINT NOT NULL');
CALL mj_safe_add_column('mj_orders', 'country_id', '`country_id` INT NULL');
CALL mj_safe_add_column('mj_orders', 'amount', '`amount` DECIMAL(12,2) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_orders', 'tax_amount', '`tax_amount` DECIMAL(12,2) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_orders', 'total_amount', '`total_amount` DECIMAL(12,2) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_orders', 'currency_code', '`currency_code` VARCHAR(8) DEFAULT ''USD''');
CALL mj_safe_add_column('mj_orders', 'status', '`status` VARCHAR(40) DEFAULT ''pending_payment''');
CALL mj_safe_add_column('mj_orders', 'payment_status', '`payment_status` VARCHAR(40) DEFAULT ''payment_disabled''');
CALL mj_safe_add_column('mj_orders', 'payment_method', '`payment_method` VARCHAR(80) NULL');
CALL mj_safe_add_column('mj_orders', 'payment_reference', '`payment_reference` VARCHAR(160) NULL');
CALL mj_safe_add_column('mj_orders', 'payment_proof_path', '`payment_proof_path` VARCHAR(255) NULL');
CALL mj_safe_add_column('mj_orders', 'payment_proof_note', '`payment_proof_note` TEXT NULL');
CALL mj_safe_add_column('mj_orders', 'payment_submitted_at', '`payment_submitted_at` DATETIME NULL');
CALL mj_safe_add_column('mj_orders', 'payment_verified_at', '`payment_verified_at` DATETIME NULL');
CALL mj_safe_add_column('mj_orders', 'payment_verified_by', '`payment_verified_by` BIGINT NULL');
CALL mj_safe_add_column('mj_orders', 'payment_rejected_at', '`payment_rejected_at` DATETIME NULL');
CALL mj_safe_add_column('mj_orders', 'payment_rejection_reason', '`payment_rejection_reason` TEXT NULL');
CALL mj_safe_add_column('mj_orders', 'delivery_method', '`delivery_method` VARCHAR(80) NULL');
CALL mj_safe_add_column('mj_orders', 'buyer_note', '`buyer_note` TEXT NULL');
CALL mj_safe_add_column('mj_orders', 'seller_note', '`seller_note` TEXT NULL');
CALL mj_safe_add_column('mj_orders', 'admin_note', '`admin_note` TEXT NULL');
CALL mj_safe_add_column('mj_orders', 'completed_at', '`completed_at` DATETIME NULL');
CALL mj_safe_add_column('mj_orders', 'cancelled_at', '`cancelled_at` DATETIME NULL');
CALL mj_safe_add_column('mj_orders', 'disputed_at', '`disputed_at` DATETIME NULL');
CALL mj_safe_add_column('mj_orders', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');
CALL mj_safe_add_column('mj_orders', 'updated_at', '`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');


CREATE TABLE IF NOT EXISTS mj_order_timeline (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  order_id BIGINT NOT NULL,
  actor_type VARCHAR(40) NOT NULL DEFAULT 'system',
  actor_id BIGINT NULL,
  action VARCHAR(80) NOT NULL,
  note TEXT NULL,
  old_status VARCHAR(60) NULL,
  new_status VARCHAR(60) NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX(order_id), INDEX(action), INDEX(created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_order_payment_proofs (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  order_id BIGINT NOT NULL,
  user_id BIGINT NULL,
  amount DECIMAL(12,2) NULL,
  currency_code VARCHAR(8) NULL,
  reference_no VARCHAR(160) NULL,
  proof_path VARCHAR(255) NOT NULL,
  original_name VARCHAR(255) NULL,
  mime_type VARCHAR(120) NULL,
  file_size BIGINT NULL,
  status VARCHAR(40) DEFAULT 'submitted',
  admin_note TEXT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX(order_id), INDEX(user_id), INDEX(status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

INSERT INTO mj_settings(setting_key,setting_value,updated_at) VALUES
('order_online_payment_enabled','0',NOW()),
('order_manual_payment_enabled','0',NOW()),
('order_payment_proof_enabled','0',NOW()),
('order_payment_instructions_visible','0',NOW()),
('order_payment_verify_role','admin_only',NOW())
ON DUPLICATE KEY UPDATE setting_value=setting_value;

-- Repair mj_orders when an older/base mj_orders table already exists.
CALL mj_safe_add_column('mj_orders', 'order_number', '`order_number` VARCHAR(80) NULL');
CALL mj_safe_add_column('mj_orders', 'quote_id', '`quote_id` BIGINT NULL');
CALL mj_safe_add_column('mj_orders', 'source_type', '`source_type` VARCHAR(40) NULL');
CALL mj_safe_add_column('mj_orders', 'source_id', '`source_id` BIGINT NULL');
CALL mj_safe_add_column('mj_orders', 'total_amount', '`total_amount` DECIMAL(12,2) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_orders', 'payment_status', '`payment_status` VARCHAR(40) DEFAULT ''payment_disabled''');
CALL mj_safe_add_column('mj_orders', 'payment_method', '`payment_method` VARCHAR(80) NULL');
CALL mj_safe_add_column('mj_orders', 'payment_reference', '`payment_reference` VARCHAR(160) NULL');
CALL mj_safe_add_column('mj_orders', 'payment_proof_path', '`payment_proof_path` VARCHAR(255) NULL');
CALL mj_safe_add_column('mj_orders', 'payment_proof_note', '`payment_proof_note` TEXT NULL');
CALL mj_safe_add_column('mj_orders', 'payment_submitted_at', '`payment_submitted_at` DATETIME NULL');
CALL mj_safe_add_column('mj_orders', 'payment_verified_at', '`payment_verified_at` DATETIME NULL');
CALL mj_safe_add_column('mj_orders', 'payment_verified_by', '`payment_verified_by` BIGINT NULL');
CALL mj_safe_add_column('mj_orders', 'payment_rejected_at', '`payment_rejected_at` DATETIME NULL');
CALL mj_safe_add_column('mj_orders', 'payment_rejection_reason', '`payment_rejection_reason` TEXT NULL');
CALL mj_safe_add_column('mj_orders', 'buyer_note', '`buyer_note` TEXT NULL');
CALL mj_safe_add_column('mj_orders', 'seller_note', '`seller_note` TEXT NULL');
CALL mj_safe_add_column('mj_orders', 'admin_note', '`admin_note` TEXT NULL');
CALL mj_safe_add_column('mj_orders', 'completed_at', '`completed_at` DATETIME NULL');
CALL mj_safe_add_column('mj_orders', 'cancelled_at', '`cancelled_at` DATETIME NULL');
CALL mj_safe_add_column('mj_orders', 'disputed_at', '`disputed_at` DATETIME NULL');
-- Convert status enum to flexible VARCHAR if table came from older schema.
ALTER TABLE mj_orders MODIFY COLUMN status VARCHAR(40) DEFAULT 'pending_payment';
ALTER TABLE mj_orders MODIFY COLUMN payment_status VARCHAR(40) DEFAULT 'payment_disabled';



-- ============================================================
-- Source: install/migration_v2.7.6_listing_paid_upgrades.sql
-- ============================================================

-- MJ Marketplace v2.7.6 Listing Paid Upgrade Requests
-- Safe additive migration.

CREATE TABLE IF NOT EXISTS mj_listing_upgrade_requests (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  listing_id BIGINT NOT NULL,
  user_id BIGINT NOT NULL,
  upgrade_type VARCHAR(40) NOT NULL,
  requested_days INT NOT NULL DEFAULT 30,
  charge_amount DECIMAL(12,2) NOT NULL DEFAULT 0,
  currency_code VARCHAR(10) NOT NULL DEFAULT 'SGD',
  payment_required TINYINT(1) NOT NULL DEFAULT 0,
  payment_status VARCHAR(50) NULL,
  status VARCHAR(50) NOT NULL DEFAULT 'pending',
  user_note TEXT NULL,
  admin_note TEXT NULL,
  reject_reason TEXT NULL,
  reviewed_by BIGINT NULL,
  requested_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  reviewed_at DATETIME NULL,
  approved_until DATETIME NULL,
  INDEX(listing_id),
  INDEX(user_id),
  INDEX(upgrade_type),
  INDEX(status),
  INDEX(requested_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CALL mj_safe_add_column('mj_listing_renewal_requests', 'payment_required', 'payment_required TINYINT(1) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_listing_renewal_requests', 'payment_amount', 'payment_amount DECIMAL(12,2) NULL');
CALL mj_safe_add_column('mj_listing_renewal_requests', 'payment_currency', 'payment_currency VARCHAR(10) NULL');
CALL mj_safe_add_column('mj_listing_renewal_requests', 'payment_status', 'payment_status VARCHAR(50) NULL');
CALL mj_safe_add_column('mj_listing_renewal_requests', 'user_note', 'user_note TEXT NULL');

INSERT INTO mj_settings(setting_key,setting_value) VALUES
('listing_renew_charge_enabled','1'),
('listing_renew_charge_amount','10.00'),
('listing_feature_charge_enabled','1'),
('listing_feature_charge_amount','15.00'),
('listing_sponsor_charge_enabled','1'),
('listing_sponsor_charge_amount','25.00'),
('listing_upgrade_currency','SGD'),
('listing_upgrade_days','30'),
('listing_upgrade_payment_note','Payment is not collected online. Admin will review your interest and contact you with payment instructions if payment is required.')
ON DUPLICATE KEY UPDATE setting_value=setting_value;



-- ============================================================
-- Source: install/migration_v2.7.7_user_limits_expiry_notifications.sql
-- ============================================================

-- MJ Marketplace v2.7.7 User Limits + Listing Expiry Notifications
-- Safe additive migration.

CREATE TABLE IF NOT EXISTS mj_user_limits (
  user_id BIGINT NOT NULL PRIMARY KEY,
  max_listings INT NULL,
  max_companies INT NULL,
  note TEXT NULL,
  updated_by BIGINT NULL,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

INSERT INTO mj_settings(setting_key,setting_value) VALUES
('default_max_listings_per_user','5'),
('default_max_companies_per_user','1'),
('listing_expiry_notify_enabled','1'),
('listing_expiry_email_enabled','1'),
('listing_expiry_notify_days_before','7,3,1,0'),
('listing_expiry_auto_renew_link_enabled','1')
ON DUPLICATE KEY UPDATE setting_value=setting_value;



-- ============================================================
-- Source: install/migration_v2.7.8_upgrade_payment_methods.sql
-- ============================================================

-- MJ Marketplace v2.7.8 Upgrade Payment Methods + Extra Slot Requests
-- Safe additive migration.

CREATE TABLE IF NOT EXISTS mj_upgrade_payment_proofs (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  request_type VARCHAR(50) NOT NULL,
  request_id BIGINT NOT NULL,
  user_id BIGINT NOT NULL,
  amount DECIMAL(12,2) NOT NULL DEFAULT 0,
  currency_code VARCHAR(10) NOT NULL DEFAULT 'SGD',
  payment_method VARCHAR(80) NULL,
  reference_no VARCHAR(190) NULL,
  proof_path VARCHAR(500) NULL,
  original_name VARCHAR(255) NULL,
  mime_type VARCHAR(120) NULL,
  file_size BIGINT NULL,
  note TEXT NULL,
  status VARCHAR(50) NOT NULL DEFAULT 'submitted',
  admin_note TEXT NULL,
  reviewed_by BIGINT NULL,
  reviewed_at DATETIME NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX(request_type, request_id),
  INDEX(user_id),
  INDEX(status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_user_limit_upgrade_requests (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  request_type VARCHAR(40) NOT NULL,
  requested_slots INT NOT NULL DEFAULT 1,
  current_limit INT NULL,
  requested_limit INT NULL,
  charge_amount DECIMAL(12,2) NOT NULL DEFAULT 0,
  currency_code VARCHAR(10) NOT NULL DEFAULT 'SGD',
  payment_required TINYINT(1) NOT NULL DEFAULT 0,
  payment_status VARCHAR(50) NULL,
  status VARCHAR(50) NOT NULL DEFAULT 'pending_payment',
  user_note TEXT NULL,
  admin_note TEXT NULL,
  reject_reason TEXT NULL,
  reviewed_by BIGINT NULL,
  requested_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  reviewed_at DATETIME NULL,
  INDEX(user_id),
  INDEX(request_type),
  INDEX(status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

INSERT INTO mj_settings(setting_key,setting_value) VALUES
('upgrade_payment_bank_enabled','0'),
('upgrade_payment_bank_label','Bank Transfer'),
('upgrade_payment_bank_details',''),
('upgrade_payment_paynow_enabled','0'),
('upgrade_payment_paynow_label','PayNow / QR'),
('upgrade_payment_paynow_details',''),
('upgrade_payment_paypal_enabled','0'),
('upgrade_payment_paypal_label','PayPal'),
('upgrade_payment_paypal_details',''),
('upgrade_payment_cash_enabled','0'),
('upgrade_payment_cash_label','Pay at Shop / Cash'),
('upgrade_payment_cash_details',''),
('upgrade_payment_proof_upload_enabled','1'),
('extra_listing_slot_charge_amount','5.00'),
('extra_company_slot_charge_amount','20.00'),
('extra_slot_currency','SGD')
ON DUPLICATE KEY UPDATE setting_value=setting_value;



-- ============================================================
-- Source: install/migration_v2.7.9_automation_frontend_invoice.sql
-- ============================================================

-- MJ Marketplace v2.7.9 Automation Center + Frontend UX Polish + Invoice Options
-- Safe additive migration.

CREATE TABLE IF NOT EXISTS mj_automation_settings (
  automation_key VARCHAR(120) NOT NULL PRIMARY KEY,
  automation_group VARCHAR(80) NOT NULL DEFAULT 'general',
  title VARCHAR(190) NOT NULL,
  description TEXT NULL,
  is_enabled TINYINT(1) NOT NULL DEFAULT 1,
  notify_user TINYINT(1) NOT NULL DEFAULT 1,
  notify_admin TINYINT(1) NOT NULL DEFAULT 1,
  email_user TINYINT(1) NOT NULL DEFAULT 0,
  run_interval_minutes INT NOT NULL DEFAULT 1440,
  repeat_limit_per_day INT NOT NULL DEFAULT 1,
  config_json TEXT NULL,
  last_run_at DATETIME NULL,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_automation_logs (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  automation_key VARCHAR(120) NOT NULL,
  automation_group VARCHAR(80) NULL,
  target_type VARCHAR(80) NULL,
  target_id BIGINT NULL,
  user_id BIGINT NULL,
  company_id BIGINT NULL,
  listing_id BIGINT NULL,
  action_taken VARCHAR(190) NULL,
  message TEXT NULL,
  status VARCHAR(40) NOT NULL DEFAULT 'success',
  error_message TEXT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX(automation_key),
  INDEX(automation_group),
  INDEX(target_type,target_id),
  INDEX(user_id),
  INDEX(company_id),
  INDEX(listing_id),
  INDEX(created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

INSERT INTO mj_settings(setting_key,setting_value) VALUES
('invoice_prefix','MJ-INV'),
('invoice_seller_name','MJ Marketplace'),
('invoice_seller_address',''),
('invoice_seller_email',''),
('invoice_tax_label','Tax'),
('invoice_tax_rate','0'),
('invoice_footer_note','Thank you for using MJ Marketplace.'),
('invoice_show_payment_note','1')
ON DUPLICATE KEY UPDATE setting_value=setting_value;



-- ============================================================
-- Source: install/migration_v2.8.0_smart_crm_customer_management.sql
-- ============================================================

-- MJ Marketplace v2.8.0 Smart CRM + Customer Management
-- Safe additive migration.

CREATE TABLE IF NOT EXISTS mj_customers (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  owner_user_id BIGINT NOT NULL,
  company_id BIGINT NULL,
  customer_user_id BIGINT NULL,
  name VARCHAR(190) NULL,
  email VARCHAR(190) NULL,
  phone VARCHAR(80) NULL,
  whatsapp VARCHAR(80) NULL,
  country_id BIGINT NULL,
  city_id BIGINT NULL,
  source_type VARCHAR(80) NULL,
  source_id BIGINT NULL,
  source_label VARCHAR(190) NULL,
  status VARCHAR(40) NOT NULL DEFAULT 'new',
  lead_score INT NOT NULL DEFAULT 0,
  tags VARCHAR(500) NULL,
  last_activity_at DATETIME NULL,
  next_followup_at DATETIME NULL,
  is_blocked TINYINT(1) NOT NULL DEFAULT 0,
  private_note TEXT NULL,
  admin_note TEXT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  UNIQUE KEY uniq_owner_email_company (owner_user_id, company_id, email),
  INDEX(owner_user_id),
  INDEX(company_id),
  INDEX(customer_user_id),
  INDEX(email),
  INDEX(phone),
  INDEX(status),
  INDEX(next_followup_at),
  INDEX(last_activity_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_customer_timeline (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  customer_id BIGINT NOT NULL,
  actor_user_id BIGINT NULL,
  event_type VARCHAR(80) NOT NULL,
  event_title VARCHAR(190) NULL,
  event_note TEXT NULL,
  related_type VARCHAR(80) NULL,
  related_id BIGINT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX(customer_id),
  INDEX(event_type),
  INDEX(related_type, related_id),
  INDEX(created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

INSERT INTO mj_settings(setting_key,setting_value) VALUES
('crm_followup_notify_enabled','1'),
('crm_followup_email_enabled','0'),
('crm_no_response_days','3'),
('crm_hot_lead_score','70'),
('crm_auto_customer_from_quote','1'),
('crm_auto_customer_from_offer','1'),
('crm_auto_customer_from_order','1'),
('crm_auto_customer_from_chat','1')
ON DUPLICATE KEY UPDATE setting_value=setting_value;



-- ============================================================
-- Source: install/migration_v2.8.1_account_center_crm_billing_trust_support.sql
-- ============================================================

-- MJ Marketplace v2.8.1 Account Center + CRM/Billing/Trust/Support
-- Safe additive migration. Run after v2.8.0 on existing installations.

CREATE TABLE IF NOT EXISTS mj_user_account_preferences (
  user_id BIGINT NOT NULL PRIMARY KEY,
  preferred_contact_method VARCHAR(40) DEFAULT 'whatsapp',
  profile_visibility VARCHAR(40) DEFAULT 'public',
  contact_visibility VARCHAR(40) DEFAULT 'login_required',
  language_preference VARCHAR(20) DEFAULT 'en',
  currency_code VARCHAR(8) DEFAULT 'SGD',
  timezone VARCHAR(80) DEFAULT 'Asia/Singapore',
  away_mode TINYINT(1) DEFAULT 0,
  away_message TEXT NULL,
  auto_reply_enabled TINYINT(1) DEFAULT 0,
  auto_reply_message TEXT NULL,
  notify_inapp TINYINT(1) DEFAULT 1,
  notify_email TINYINT(1) DEFAULT 1,
  notify_sms TINYINT(1) DEFAULT 0,
  notify_whatsapp TINYINT(1) DEFAULT 0,
  do_not_disturb TINYINT(1) DEFAULT 0,
  daily_digest TINYINT(1) DEFAULT 0,
  important_alerts_only TINYINT(1) DEFAULT 0,
  security_login_alert TINYINT(1) DEFAULT 1,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_user_account_preferences (fresh installer safe).
CALL mj_safe_add_column('mj_user_account_preferences', 'preferred_contact_method', '`preferred_contact_method` VARCHAR(40) DEFAULT ''whatsapp''');
CALL mj_safe_add_column('mj_user_account_preferences', 'profile_visibility', '`profile_visibility` VARCHAR(40) DEFAULT ''public''');
CALL mj_safe_add_column('mj_user_account_preferences', 'contact_visibility', '`contact_visibility` VARCHAR(40) DEFAULT ''login_required''');
CALL mj_safe_add_column('mj_user_account_preferences', 'language_preference', '`language_preference` VARCHAR(20) DEFAULT ''en''');
CALL mj_safe_add_column('mj_user_account_preferences', 'currency_code', '`currency_code` VARCHAR(8) DEFAULT ''SGD''');
CALL mj_safe_add_column('mj_user_account_preferences', 'timezone', '`timezone` VARCHAR(80) DEFAULT ''Asia/Singapore''');
CALL mj_safe_add_column('mj_user_account_preferences', 'away_mode', '`away_mode` TINYINT(1) DEFAULT 0');
CALL mj_safe_add_column('mj_user_account_preferences', 'away_message', '`away_message` TEXT NULL');
CALL mj_safe_add_column('mj_user_account_preferences', 'auto_reply_enabled', '`auto_reply_enabled` TINYINT(1) DEFAULT 0');
CALL mj_safe_add_column('mj_user_account_preferences', 'auto_reply_message', '`auto_reply_message` TEXT NULL');
CALL mj_safe_add_column('mj_user_account_preferences', 'notify_inapp', '`notify_inapp` TINYINT(1) DEFAULT 1');
CALL mj_safe_add_column('mj_user_account_preferences', 'notify_email', '`notify_email` TINYINT(1) DEFAULT 1');
CALL mj_safe_add_column('mj_user_account_preferences', 'notify_sms', '`notify_sms` TINYINT(1) DEFAULT 0');
CALL mj_safe_add_column('mj_user_account_preferences', 'notify_whatsapp', '`notify_whatsapp` TINYINT(1) DEFAULT 0');
CALL mj_safe_add_column('mj_user_account_preferences', 'do_not_disturb', '`do_not_disturb` TINYINT(1) DEFAULT 0');
CALL mj_safe_add_column('mj_user_account_preferences', 'daily_digest', '`daily_digest` TINYINT(1) DEFAULT 0');
CALL mj_safe_add_column('mj_user_account_preferences', 'important_alerts_only', '`important_alerts_only` TINYINT(1) DEFAULT 0');
CALL mj_safe_add_column('mj_user_account_preferences', 'security_login_alert', '`security_login_alert` TINYINT(1) DEFAULT 1');
CALL mj_safe_add_column('mj_user_account_preferences', 'updated_at', '`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');


CREATE TABLE IF NOT EXISTS mj_saved_searches (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  title VARCHAR(190) NOT NULL,
  search_url VARCHAR(500) NULL,
  filters_json TEXT NULL,
  alert_enabled TINYINT(1) DEFAULT 1,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX(user_id),
  INDEX(alert_enabled)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_saved_searches (fresh installer safe).
CALL mj_safe_add_column('mj_saved_searches', 'user_id', '`user_id` BIGINT NOT NULL');
CALL mj_safe_add_column('mj_saved_searches', 'title', '`title` VARCHAR(190) NOT NULL');
CALL mj_safe_add_column('mj_saved_searches', 'search_url', '`search_url` VARCHAR(500) NULL');
CALL mj_safe_add_column('mj_saved_searches', 'filters_json', '`filters_json` TEXT NULL');
CALL mj_safe_add_column('mj_saved_searches', 'alert_enabled', '`alert_enabled` TINYINT(1) DEFAULT 1');
CALL mj_safe_add_column('mj_saved_searches', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');


CREATE TABLE IF NOT EXISTS mj_blocked_users (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  blocked_user_id BIGINT NULL,
  blocked_email VARCHAR(190) NULL,
  reason VARCHAR(255) NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  UNIQUE KEY uniq_block_user (user_id, blocked_user_id),
  INDEX(user_id),
  INDEX(blocked_email)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_blocked_users (fresh installer safe).
CALL mj_safe_add_column('mj_blocked_users', 'user_id', '`user_id` BIGINT NOT NULL');
CALL mj_safe_add_column('mj_blocked_users', 'blocked_user_id', '`blocked_user_id` BIGINT NULL');
CALL mj_safe_add_column('mj_blocked_users', 'blocked_email', '`blocked_email` VARCHAR(190) NULL');
CALL mj_safe_add_column('mj_blocked_users', 'reason', '`reason` VARCHAR(255) NULL');
CALL mj_safe_add_column('mj_blocked_users', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');


CREATE TABLE IF NOT EXISTS mj_user_activity_logs (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  action VARCHAR(120) NOT NULL,
  description TEXT NULL,
  ip_address VARCHAR(80) NULL,
  user_agent VARCHAR(255) NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX(user_id),
  INDEX(action),
  INDEX(created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_user_activity_logs (fresh installer safe).
CALL mj_safe_add_column('mj_user_activity_logs', 'user_id', '`user_id` BIGINT NOT NULL');
CALL mj_safe_add_column('mj_user_activity_logs', 'action', '`action` VARCHAR(120) NOT NULL');
CALL mj_safe_add_column('mj_user_activity_logs', 'description', '`description` TEXT NULL');
CALL mj_safe_add_column('mj_user_activity_logs', 'ip_address', '`ip_address` VARCHAR(80) NULL');
CALL mj_safe_add_column('mj_user_activity_logs', 'user_agent', '`user_agent` VARCHAR(255) NULL');
CALL mj_safe_add_column('mj_user_activity_logs', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');


CREATE TABLE IF NOT EXISTS mj_payment_requests (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  company_id BIGINT NULL,
  request_type VARCHAR(80) DEFAULT 'manual',
  reference_type VARCHAR(80) NULL,
  reference_id BIGINT NULL,
  title VARCHAR(190) NOT NULL,
  description TEXT NULL,
  amount DECIMAL(12,2) NOT NULL DEFAULT 0,
  tax_amount DECIMAL(12,2) NOT NULL DEFAULT 0,
  total_amount DECIMAL(12,2) NOT NULL DEFAULT 0,
  currency_code VARCHAR(8) DEFAULT 'SGD',
  status VARCHAR(40) DEFAULT 'pending',
  due_date DATE NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  INDEX(user_id),
  INDEX(status),
  INDEX(request_type)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_payment_requests (fresh installer safe).
CALL mj_safe_add_column('mj_payment_requests', 'user_id', '`user_id` BIGINT NOT NULL');
CALL mj_safe_add_column('mj_payment_requests', 'company_id', '`company_id` BIGINT NULL');
CALL mj_safe_add_column('mj_payment_requests', 'request_type', '`request_type` VARCHAR(80) DEFAULT ''manual''');
CALL mj_safe_add_column('mj_payment_requests', 'reference_type', '`reference_type` VARCHAR(80) NULL');
CALL mj_safe_add_column('mj_payment_requests', 'reference_id', '`reference_id` BIGINT NULL');
CALL mj_safe_add_column('mj_payment_requests', 'title', '`title` VARCHAR(190) NOT NULL');
CALL mj_safe_add_column('mj_payment_requests', 'description', '`description` TEXT NULL');
CALL mj_safe_add_column('mj_payment_requests', 'amount', '`amount` DECIMAL(12,2) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_payment_requests', 'tax_amount', '`tax_amount` DECIMAL(12,2) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_payment_requests', 'total_amount', '`total_amount` DECIMAL(12,2) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_payment_requests', 'currency_code', '`currency_code` VARCHAR(8) DEFAULT ''SGD''');
CALL mj_safe_add_column('mj_payment_requests', 'status', '`status` VARCHAR(40) DEFAULT ''pending''');
CALL mj_safe_add_column('mj_payment_requests', 'due_date', '`due_date` DATE NULL');
CALL mj_safe_add_column('mj_payment_requests', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');
CALL mj_safe_add_column('mj_payment_requests', 'updated_at', '`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');


CREATE TABLE IF NOT EXISTS mj_invoices (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  company_id BIGINT NULL,
  invoice_no VARCHAR(80) UNIQUE,
  invoice_type VARCHAR(40) DEFAULT 'invoice',
  title VARCHAR(190) NULL,
  amount DECIMAL(12,2) NOT NULL DEFAULT 0,
  tax_amount DECIMAL(12,2) NOT NULL DEFAULT 0,
  total_amount DECIMAL(12,2) NOT NULL DEFAULT 0,
  currency_code VARCHAR(8) DEFAULT 'SGD',
  status VARCHAR(40) DEFAULT 'draft',
  due_date DATE NULL,
  paid_at DATETIME NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX(user_id),
  INDEX(status),
  INDEX(invoice_no)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_invoices (fresh installer safe).
CALL mj_safe_add_column('mj_invoices', 'user_id', '`user_id` BIGINT NOT NULL');
CALL mj_safe_add_column('mj_invoices', 'company_id', '`company_id` BIGINT NULL');
CALL mj_safe_add_column('mj_invoices', 'invoice_no', '`invoice_no` VARCHAR(80) UNIQUE');
CALL mj_safe_add_column('mj_invoices', 'invoice_type', '`invoice_type` VARCHAR(40) DEFAULT ''invoice''');
CALL mj_safe_add_column('mj_invoices', 'title', '`title` VARCHAR(190) NULL');
CALL mj_safe_add_column('mj_invoices', 'amount', '`amount` DECIMAL(12,2) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_invoices', 'tax_amount', '`tax_amount` DECIMAL(12,2) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_invoices', 'total_amount', '`total_amount` DECIMAL(12,2) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_invoices', 'currency_code', '`currency_code` VARCHAR(8) DEFAULT ''SGD''');
CALL mj_safe_add_column('mj_invoices', 'status', '`status` VARCHAR(40) DEFAULT ''draft''');
CALL mj_safe_add_column('mj_invoices', 'due_date', '`due_date` DATE NULL');
CALL mj_safe_add_column('mj_invoices', 'paid_at', '`paid_at` DATETIME NULL');
CALL mj_safe_add_column('mj_invoices', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');


CREATE TABLE IF NOT EXISTS mj_refund_requests (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  invoice_id BIGINT NULL,
  payment_request_id BIGINT NULL,
  reason TEXT NULL,
  amount DECIMAL(12,2) DEFAULT 0,
  currency_code VARCHAR(8) DEFAULT 'SGD',
  status VARCHAR(40) DEFAULT 'pending',
  admin_note TEXT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX(user_id),
  INDEX(status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_refund_requests (fresh installer safe).
CALL mj_safe_add_column('mj_refund_requests', 'user_id', '`user_id` BIGINT NOT NULL');
CALL mj_safe_add_column('mj_refund_requests', 'invoice_id', '`invoice_id` BIGINT NULL');
CALL mj_safe_add_column('mj_refund_requests', 'payment_request_id', '`payment_request_id` BIGINT NULL');
CALL mj_safe_add_column('mj_refund_requests', 'reason', '`reason` TEXT NULL');
CALL mj_safe_add_column('mj_refund_requests', 'amount', '`amount` DECIMAL(12,2) DEFAULT 0');
CALL mj_safe_add_column('mj_refund_requests', 'currency_code', '`currency_code` VARCHAR(8) DEFAULT ''SGD''');
CALL mj_safe_add_column('mj_refund_requests', 'status', '`status` VARCHAR(40) DEFAULT ''pending''');
CALL mj_safe_add_column('mj_refund_requests', 'admin_note', '`admin_note` TEXT NULL');
CALL mj_safe_add_column('mj_refund_requests', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');


CREATE TABLE IF NOT EXISTS mj_verification_requests (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  company_id BIGINT NULL,
  verification_type VARCHAR(80) NOT NULL,
  document_path VARCHAR(255) NULL,
  note TEXT NULL,
  status VARCHAR(40) DEFAULT 'pending',
  admin_note TEXT NULL,
  expires_at DATE NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  reviewed_at DATETIME NULL,
  INDEX(user_id),
  INDEX(company_id),
  INDEX(status),
  INDEX(verification_type)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_verification_requests (fresh installer safe).
CALL mj_safe_add_column('mj_verification_requests', 'user_id', '`user_id` BIGINT NOT NULL');
CALL mj_safe_add_column('mj_verification_requests', 'company_id', '`company_id` BIGINT NULL');
CALL mj_safe_add_column('mj_verification_requests', 'verification_type', '`verification_type` VARCHAR(80) NOT NULL');
CALL mj_safe_add_column('mj_verification_requests', 'document_path', '`document_path` VARCHAR(255) NULL');
CALL mj_safe_add_column('mj_verification_requests', 'note', '`note` TEXT NULL');
CALL mj_safe_add_column('mj_verification_requests', 'status', '`status` VARCHAR(40) DEFAULT ''pending''');
CALL mj_safe_add_column('mj_verification_requests', 'admin_note', '`admin_note` TEXT NULL');
CALL mj_safe_add_column('mj_verification_requests', 'expires_at', '`expires_at` DATE NULL');
CALL mj_safe_add_column('mj_verification_requests', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');
CALL mj_safe_add_column('mj_verification_requests', 'reviewed_at', '`reviewed_at` DATETIME NULL');


CREATE TABLE IF NOT EXISTS mj_support_ticket_messages (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  ticket_id BIGINT NOT NULL,
  sender_type VARCHAR(20) DEFAULT 'user',
  sender_user_id BIGINT NULL,
  sender_admin_id BIGINT NULL,
  message TEXT NOT NULL,
  attachment_path VARCHAR(255) NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX(ticket_id),
  INDEX(sender_type)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_support_ticket_messages (fresh installer safe).
CALL mj_safe_add_column('mj_support_ticket_messages', 'ticket_id', '`ticket_id` BIGINT NOT NULL');
CALL mj_safe_add_column('mj_support_ticket_messages', 'sender_type', '`sender_type` VARCHAR(20) DEFAULT ''user''');
CALL mj_safe_add_column('mj_support_ticket_messages', 'sender_user_id', '`sender_user_id` BIGINT NULL');
CALL mj_safe_add_column('mj_support_ticket_messages', 'sender_admin_id', '`sender_admin_id` BIGINT NULL');
CALL mj_safe_add_column('mj_support_ticket_messages', 'message', '`message` TEXT NOT NULL');
CALL mj_safe_add_column('mj_support_ticket_messages', 'attachment_path', '`attachment_path` VARCHAR(255) NULL');
CALL mj_safe_add_column('mj_support_ticket_messages', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');


CREATE TABLE IF NOT EXISTS mj_quick_reply_templates (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  title VARCHAR(190) NOT NULL,
  message TEXT NOT NULL,
  is_active TINYINT(1) DEFAULT 1,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX(user_id),
  INDEX(is_active)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_quick_reply_templates (fresh installer safe).
CALL mj_safe_add_column('mj_quick_reply_templates', 'user_id', '`user_id` BIGINT NOT NULL');
CALL mj_safe_add_column('mj_quick_reply_templates', 'title', '`title` VARCHAR(190) NOT NULL');
CALL mj_safe_add_column('mj_quick_reply_templates', 'message', '`message` TEXT NOT NULL');
CALL mj_safe_add_column('mj_quick_reply_templates', 'is_active', '`is_active` TINYINT(1) DEFAULT 1');
CALL mj_safe_add_column('mj_quick_reply_templates', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');


INSERT INTO mj_settings(setting_key,setting_value) VALUES
('account_center_enabled','1'),
('crm_leads_enabled','1'),
('billing_payments_enabled','1'),
('trust_verification_enabled','1'),
('support_center_enabled','1'),
('account_center_profile_score_enabled','1'),
('crm_missed_lead_alert_hours','24'),
('crm_auto_reply_enabled','1'),
('support_ticket_upload_enabled','1'),
('billing_tax_enabled','0'),
('billing_tax_label','GST/VAT'),
('billing_default_tax_rate','0'),
('privacy_data_download_enabled','1'),
('privacy_delete_request_enabled','1')
ON DUPLICATE KEY UPDATE setting_value=setting_value;

INSERT INTO mj_site_modules(module_code,module_name,description,is_enabled) VALUES
('account_center','Account Center','Profile, company, wallet, security, privacy and user preferences',1),
('crm_leads','CRM / Leads','Lead inbox, follow-up reminders, customer timeline and CRM tools',1),
('billing_payments','Billing & Payments','Wallet, invoices, payment requests, receipts, refunds and tax settings',1),
('trust_verification','Trust & Verification','Verification, trust badges, safety, reports and device security',1),
('support_center','Help & Support','Tickets, FAQ, guides, attachments and admin replies',1),
('saved_search_alerts','Saved Search Alerts','Notify users when matching listings are added',1),
('seller_automation','Seller Automation','Auto reply, away mode, low performance alerts and renew reminders',1),
('company_team','Company Team','Staff users, roles, branches and lead assignment',1),
('marketing_tools','Marketing Tools','Share poster, QR poster, SEO preview and AI listing helper',1)
ON DUPLICATE KEY UPDATE module_name=VALUES(module_name), description=VALUES(description), is_enabled=VALUES(is_enabled);



-- ============================================================
-- Source: install/migration_v2.8.4_admin_user_list_access_controls.sql
-- ============================================================

-- MJ Marketplace v2.8.4 Admin User List Access Controls
-- Allows admin to enable/disable billing, wallet, payment proof, renew, sponsored, featured and extra slots per user.

CREATE TABLE IF NOT EXISTS mj_user_feature_access (
    user_id BIGINT NOT NULL PRIMARY KEY,
    account_center TINYINT(1) NOT NULL DEFAULT 1,
    crm_leads TINYINT(1) NOT NULL DEFAULT 1,
    billing_payments TINYINT(1) NOT NULL DEFAULT 1,
    wallet TINYINT(1) NOT NULL DEFAULT 1,
    payment_proof_upload TINYINT(1) NOT NULL DEFAULT 1,
    renew_listing TINYINT(1) NOT NULL DEFAULT 1,
    featured_listing TINYINT(1) NOT NULL DEFAULT 1,
    sponsored_listing TINYINT(1) NOT NULL DEFAULT 1,
    extra_listing_slots TINYINT(1) NOT NULL DEFAULT 1,
    extra_company_slots TINYINT(1) NOT NULL DEFAULT 1,
    trust_verification TINYINT(1) NOT NULL DEFAULT 1,
    support_center TINYINT(1) NOT NULL DEFAULT 1,
    admin_note TEXT NULL,
    updated_by BIGINT NULL,
    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX(updated_by)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_user_feature_access (fresh installer safe).
CALL mj_safe_add_column('mj_user_feature_access', 'account_center', '`account_center` TINYINT(1) NOT NULL DEFAULT 1');
CALL mj_safe_add_column('mj_user_feature_access', 'crm_leads', '`crm_leads` TINYINT(1) NOT NULL DEFAULT 1');
CALL mj_safe_add_column('mj_user_feature_access', 'billing_payments', '`billing_payments` TINYINT(1) NOT NULL DEFAULT 1');
CALL mj_safe_add_column('mj_user_feature_access', 'wallet', '`wallet` TINYINT(1) NOT NULL DEFAULT 1');
CALL mj_safe_add_column('mj_user_feature_access', 'payment_proof_upload', '`payment_proof_upload` TINYINT(1) NOT NULL DEFAULT 1');
CALL mj_safe_add_column('mj_user_feature_access', 'renew_listing', '`renew_listing` TINYINT(1) NOT NULL DEFAULT 1');
CALL mj_safe_add_column('mj_user_feature_access', 'featured_listing', '`featured_listing` TINYINT(1) NOT NULL DEFAULT 1');
CALL mj_safe_add_column('mj_user_feature_access', 'sponsored_listing', '`sponsored_listing` TINYINT(1) NOT NULL DEFAULT 1');
CALL mj_safe_add_column('mj_user_feature_access', 'extra_listing_slots', '`extra_listing_slots` TINYINT(1) NOT NULL DEFAULT 1');
CALL mj_safe_add_column('mj_user_feature_access', 'extra_company_slots', '`extra_company_slots` TINYINT(1) NOT NULL DEFAULT 1');
CALL mj_safe_add_column('mj_user_feature_access', 'trust_verification', '`trust_verification` TINYINT(1) NOT NULL DEFAULT 1');
CALL mj_safe_add_column('mj_user_feature_access', 'support_center', '`support_center` TINYINT(1) NOT NULL DEFAULT 1');
CALL mj_safe_add_column('mj_user_feature_access', 'admin_note', '`admin_note` TEXT NULL');
CALL mj_safe_add_column('mj_user_feature_access', 'updated_by', '`updated_by` BIGINT NULL');
CALL mj_safe_add_column('mj_user_feature_access', 'updated_at', '`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');




-- ============================================================
-- Source: install/migration_v2.8.8_complete_cleanup_frontend_user_admin.sql
-- ============================================================

-- MJ Marketplace v2.8.8 Complete Frontend/User/Admin/Automation Cleanup
-- Safe migration: creates missing tables for country themes, company page builder, appointment booking,
-- saved searches, admin notes and system run logs. Existing data is preserved.

CREATE TABLE IF NOT EXISTS mj_company_page_settings (
  company_id BIGINT NOT NULL PRIMARY KEY,
  layout_style VARCHAR(80) DEFAULT 'modern_card',
  theme_primary VARCHAR(32) NULL,
  theme_secondary VARCHAR(32) NULL,
  theme_accent VARCHAR(32) NULL,
  cover_image VARCHAR(255) NULL,
  video_url VARCHAR(255) NULL,
  show_reviews TINYINT(1) NOT NULL DEFAULT 1,
  show_booking TINYINT(1) NOT NULL DEFAULT 0,
  seo_title VARCHAR(255) NULL,
  seo_description TEXT NULL,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_company_page_settings (fresh installer safe).
CALL mj_safe_add_column('mj_company_page_settings', 'layout_style', '`layout_style` VARCHAR(80) DEFAULT ''modern_card''');
CALL mj_safe_add_column('mj_company_page_settings', 'theme_primary', '`theme_primary` VARCHAR(32) NULL');
CALL mj_safe_add_column('mj_company_page_settings', 'theme_secondary', '`theme_secondary` VARCHAR(32) NULL');
CALL mj_safe_add_column('mj_company_page_settings', 'theme_accent', '`theme_accent` VARCHAR(32) NULL');
CALL mj_safe_add_column('mj_company_page_settings', 'cover_image', '`cover_image` VARCHAR(255) NULL');
CALL mj_safe_add_column('mj_company_page_settings', 'video_url', '`video_url` VARCHAR(255) NULL');
CALL mj_safe_add_column('mj_company_page_settings', 'show_reviews', '`show_reviews` TINYINT(1) NOT NULL DEFAULT 1');
CALL mj_safe_add_column('mj_company_page_settings', 'show_booking', '`show_booking` TINYINT(1) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_company_page_settings', 'seo_title', '`seo_title` VARCHAR(255) NULL');
CALL mj_safe_add_column('mj_company_page_settings', 'seo_description', '`seo_description` TEXT NULL');
CALL mj_safe_add_column('mj_company_page_settings', 'updated_at', '`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');


CREATE TABLE IF NOT EXISTS mj_company_page_sections (
  id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
  company_id BIGINT NOT NULL,
  section_key VARCHAR(80) NOT NULL,
  section_title VARCHAR(255) NULL,
  section_content MEDIUMTEXT NULL,
  is_enabled TINYINT(1) NOT NULL DEFAULT 1,
  sort_order INT NOT NULL DEFAULT 0,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  INDEX(company_id), INDEX(section_key)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_company_page_sections (fresh installer safe).
CALL mj_safe_add_column('mj_company_page_sections', 'company_id', '`company_id` BIGINT NOT NULL');
CALL mj_safe_add_column('mj_company_page_sections', 'section_key', '`section_key` VARCHAR(80) NOT NULL');
CALL mj_safe_add_column('mj_company_page_sections', 'section_title', '`section_title` VARCHAR(255) NULL');
CALL mj_safe_add_column('mj_company_page_sections', 'section_content', '`section_content` MEDIUMTEXT NULL');
CALL mj_safe_add_column('mj_company_page_sections', 'is_enabled', '`is_enabled` TINYINT(1) NOT NULL DEFAULT 1');
CALL mj_safe_add_column('mj_company_page_sections', 'sort_order', '`sort_order` INT NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_company_page_sections', 'updated_at', '`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');


CREATE TABLE IF NOT EXISTS mj_company_booking_settings (
  company_id BIGINT NOT NULL PRIMARY KEY,
  is_enabled TINYINT(1) NOT NULL DEFAULT 0,
  booking_type VARCHAR(80) DEFAULT 'appointment',
  slot_duration INT NOT NULL DEFAULT 30,
  buffer_minutes INT NOT NULL DEFAULT 0,
  max_bookings_per_day INT NOT NULL DEFAULT 20,
  advance_days INT NOT NULL DEFAULT 30,
  same_day_booking TINYINT(1) NOT NULL DEFAULT 1,
  manual_approval TINYINT(1) NOT NULL DEFAULT 1,
  allow_reschedule TINYINT(1) NOT NULL DEFAULT 1,
  allow_cancel TINYINT(1) NOT NULL DEFAULT 1,
  business_hours_json TEXT NULL,
  closed_dates_json TEXT NULL,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_company_booking_settings (fresh installer safe).
CALL mj_safe_add_column('mj_company_booking_settings', 'is_enabled', '`is_enabled` TINYINT(1) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_company_booking_settings', 'booking_type', '`booking_type` VARCHAR(80) DEFAULT ''appointment''');
CALL mj_safe_add_column('mj_company_booking_settings', 'slot_duration', '`slot_duration` INT NOT NULL DEFAULT 30');
CALL mj_safe_add_column('mj_company_booking_settings', 'buffer_minutes', '`buffer_minutes` INT NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_company_booking_settings', 'max_bookings_per_day', '`max_bookings_per_day` INT NOT NULL DEFAULT 20');
CALL mj_safe_add_column('mj_company_booking_settings', 'advance_days', '`advance_days` INT NOT NULL DEFAULT 30');
CALL mj_safe_add_column('mj_company_booking_settings', 'same_day_booking', '`same_day_booking` TINYINT(1) NOT NULL DEFAULT 1');
CALL mj_safe_add_column('mj_company_booking_settings', 'manual_approval', '`manual_approval` TINYINT(1) NOT NULL DEFAULT 1');
CALL mj_safe_add_column('mj_company_booking_settings', 'allow_reschedule', '`allow_reschedule` TINYINT(1) NOT NULL DEFAULT 1');
CALL mj_safe_add_column('mj_company_booking_settings', 'allow_cancel', '`allow_cancel` TINYINT(1) NOT NULL DEFAULT 1');
CALL mj_safe_add_column('mj_company_booking_settings', 'business_hours_json', '`business_hours_json` TEXT NULL');
CALL mj_safe_add_column('mj_company_booking_settings', 'closed_dates_json', '`closed_dates_json` TEXT NULL');
CALL mj_safe_add_column('mj_company_booking_settings', 'updated_at', '`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');


CREATE TABLE IF NOT EXISTS mj_appointments (
  id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT NULL,
  seller_user_id BIGINT NULL,
  company_id BIGINT NULL,
  listing_id BIGINT NULL,
  service_id BIGINT NULL,
  branch_id BIGINT NULL,
  customer_name VARCHAR(190) NULL,
  customer_email VARCHAR(190) NULL,
  customer_phone VARCHAR(80) NULL,
  appointment_datetime DATETIME NULL,
  duration_minutes INT NOT NULL DEFAULT 30,
  status VARCHAR(50) NOT NULL DEFAULT 'pending',
  note TEXT NULL,
  admin_note TEXT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  INDEX(user_id), INDEX(seller_user_id), INDEX(company_id), INDEX(status), INDEX(appointment_datetime)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_appointments (fresh installer safe).
CALL mj_safe_add_column('mj_appointments', 'user_id', '`user_id` BIGINT NULL');
CALL mj_safe_add_column('mj_appointments', 'seller_user_id', '`seller_user_id` BIGINT NULL');
CALL mj_safe_add_column('mj_appointments', 'company_id', '`company_id` BIGINT NULL');
CALL mj_safe_add_column('mj_appointments', 'listing_id', '`listing_id` BIGINT NULL');
CALL mj_safe_add_column('mj_appointments', 'service_id', '`service_id` BIGINT NULL');
CALL mj_safe_add_column('mj_appointments', 'branch_id', '`branch_id` BIGINT NULL');
CALL mj_safe_add_column('mj_appointments', 'customer_name', '`customer_name` VARCHAR(190) NULL');
CALL mj_safe_add_column('mj_appointments', 'customer_email', '`customer_email` VARCHAR(190) NULL');
CALL mj_safe_add_column('mj_appointments', 'customer_phone', '`customer_phone` VARCHAR(80) NULL');
CALL mj_safe_add_column('mj_appointments', 'appointment_datetime', '`appointment_datetime` DATETIME NULL');
CALL mj_safe_add_column('mj_appointments', 'duration_minutes', '`duration_minutes` INT NOT NULL DEFAULT 30');
CALL mj_safe_add_column('mj_appointments', 'status', '`status` VARCHAR(50) NOT NULL DEFAULT ''pending''');
CALL mj_safe_add_column('mj_appointments', 'note', '`note` TEXT NULL');
CALL mj_safe_add_column('mj_appointments', 'admin_note', '`admin_note` TEXT NULL');
CALL mj_safe_add_column('mj_appointments', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');
CALL mj_safe_add_column('mj_appointments', 'updated_at', '`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');


CREATE TABLE IF NOT EXISTS mj_admin_notes (
  id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
  admin_id BIGINT NULL,
  related_type VARCHAR(80) NOT NULL,
  related_id BIGINT NOT NULL,
  note TEXT NOT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX(related_type,related_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_admin_notes (fresh installer safe).
CALL mj_safe_add_column('mj_admin_notes', 'admin_id', '`admin_id` BIGINT NULL');
CALL mj_safe_add_column('mj_admin_notes', 'related_type', '`related_type` VARCHAR(80) NOT NULL');
CALL mj_safe_add_column('mj_admin_notes', 'related_id', '`related_id` BIGINT NOT NULL');
CALL mj_safe_add_column('mj_admin_notes', 'note', '`note` TEXT NOT NULL');
CALL mj_safe_add_column('mj_admin_notes', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');


CREATE TABLE IF NOT EXISTS mj_saved_searches (
  id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  title VARCHAR(190) NULL,
  query_json TEXT NULL,
  alert_enabled TINYINT(1) NOT NULL DEFAULT 1,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX(user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_saved_searches (fresh installer safe).
CALL mj_safe_add_column('mj_saved_searches', 'user_id', '`user_id` BIGINT NOT NULL');
CALL mj_safe_add_column('mj_saved_searches', 'title', '`title` VARCHAR(190) NULL');
CALL mj_safe_add_column('mj_saved_searches', 'query_json', '`query_json` TEXT NULL');
CALL mj_safe_add_column('mj_saved_searches', 'alert_enabled', '`alert_enabled` TINYINT(1) NOT NULL DEFAULT 1');
CALL mj_safe_add_column('mj_saved_searches', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');


CREATE TABLE IF NOT EXISTS mj_system_run_logs (
  id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
  task_key VARCHAR(120) NOT NULL,
  status VARCHAR(50) NOT NULL DEFAULT 'success',
  message TEXT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX(task_key)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_system_run_logs (fresh installer safe).
CALL mj_safe_add_column('mj_system_run_logs', 'task_key', '`task_key` VARCHAR(120) NOT NULL');
CALL mj_safe_add_column('mj_system_run_logs', 'status', '`status` VARCHAR(50) NOT NULL DEFAULT ''success''');
CALL mj_safe_add_column('mj_system_run_logs', 'message', '`message` TEXT NULL');
CALL mj_safe_add_column('mj_system_run_logs', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');


-- Add these columns to mj_countries if your MySQL supports ALTER ADD COLUMN checks manually.
-- If column already exists, skip that ALTER line.
-- ALTER TABLE mj_countries ADD COLUMN theme_primary VARCHAR(32) NULL;
-- ALTER TABLE mj_countries ADD COLUMN theme_secondary VARCHAR(32) NULL;
-- ALTER TABLE mj_countries ADD COLUMN theme_accent VARCHAR(32) NULL;
-- ALTER TABLE mj_countries ADD COLUMN theme_text VARCHAR(32) NULL;
-- ALTER TABLE mj_countries ADD COLUMN theme_gradient VARCHAR(255) NULL;
-- ALTER TABLE mj_countries ADD COLUMN use_flag_theme TINYINT(1) NOT NULL DEFAULT 1;

INSERT INTO mj_site_modules(module_code,module_name,description,is_enabled) VALUES
('selling_center','Selling Center','Unified seller dashboard, listings, companies, CRM, booking and upgrades',1),
('buying_center','Buying Center','Unified buyer dashboard, saved items, offers, quotes, appointments and reports',1),
('appointment_booking','Appointment Booking','Company/listing appointment booking and appointment center',1),
('company_page_builder','Company Page Builder','Flexible company detail page colors, sections, SEO and booking button',1),
('saved_search_alerts','Saved Searches & Price Alerts','Saved searches, price alerts and matching listing alerts',1),
('qr_share_tools','QR / Share Poster Tools','QR codes and share poster tools for listing/company marketing',1),
('reviews_ratings','Reviews & Ratings','Reviews, ratings, moderation and trust badges',1),
('import_export','Import / Export','CSV import/export controls for admin and allowed users',1)
ON DUPLICATE KEY UPDATE module_name=VALUES(module_name), description=VALUES(description), is_enabled=VALUES(is_enabled);



-- ============================================================
-- Source: install/migration_v2.8.9_admin_command_center.sql
-- ============================================================

-- MJ Marketplace v2.8.9 Admin Command Center
-- Safe additive migration. Adds admin dashboard/module metadata only.

SET @mj_sql := IF((SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='mj_site_modules' AND COLUMN_NAME='module_group')=0, 'ALTER TABLE mj_site_modules ADD COLUMN module_group VARCHAR(80) NULL', 'SELECT 1'); PREPARE stmt FROM @mj_sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @mj_sql := IF((SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='mj_site_modules' AND COLUMN_NAME='sort_order')=0, 'ALTER TABLE mj_site_modules ADD COLUMN sort_order INT NOT NULL DEFAULT 0', 'SELECT 1'); PREPARE stmt FROM @mj_sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @mj_sql := IF((SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='mj_site_modules' AND COLUMN_NAME='admin_icon')=0, 'ALTER TABLE mj_site_modules ADD COLUMN admin_icon VARCHAR(80) NULL', 'SELECT 1'); PREPARE stmt FROM @mj_sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @mj_sql := IF((SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='mj_site_modules' AND COLUMN_NAME='is_core')=0, 'ALTER TABLE mj_site_modules ADD COLUMN is_core TINYINT(1) NOT NULL DEFAULT 0', 'SELECT 1'); PREPARE stmt FROM @mj_sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;

CREATE TABLE IF NOT EXISTS mj_admin_saved_filters (
  id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
  admin_id BIGINT NULL,
  filter_name VARCHAR(190) NOT NULL,
  page_key VARCHAR(120) NOT NULL,
  filter_json TEXT NULL,
  is_default TINYINT(1) NOT NULL DEFAULT 0,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX(admin_id),
  INDEX(page_key)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_admin_dashboard_widgets (
  id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
  widget_key VARCHAR(120) NOT NULL UNIQUE,
  widget_title VARCHAR(190) NOT NULL,
  widget_group VARCHAR(80) NULL,
  is_enabled TINYINT(1) NOT NULL DEFAULT 1,
  sort_order INT NOT NULL DEFAULT 0,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_admin_task_notes (
  id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
  admin_id BIGINT NULL,
  task_title VARCHAR(190) NOT NULL,
  task_status VARCHAR(50) NOT NULL DEFAULT 'open',
  due_at DATETIME NULL,
  note TEXT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  INDEX(admin_id),
  INDEX(task_status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

INSERT INTO mj_site_modules(module_code,module_group,module_name,admin_icon,sort_order,is_enabled,is_core,description) VALUES
('listings','Marketplace','Listings','fas fa-tags',10,1,1,'Listing posting, approval, expiry, renewals and upgrades'),
('companies','Company','Companies','fas fa-building',20,1,1,'Company profiles, branding, branches and company page builder'),
('crm','Users & CRM','CRM / Leads','fas fa-address-book',30,1,0,'Customer management, lead inbox and timelines'),
('billing','Billing','Billing & Payments','fas fa-money-check-alt',40,1,1,'Payments, proof uploads, invoices, receipts and finance'),
('wallet','Billing','Wallet / Credits','fas fa-wallet',50,1,0,'Wallet ledger, credits and credit usage'),
('verification','Trust & Safety','Trust & Verification','fas fa-shield-alt',60,1,0,'Verification requests, trusted badges and privacy center'),
('support','Users & CRM','Help & Support','fas fa-headset',70,1,0,'Support tickets, FAQ and admin replies'),
('bookings','Deals / Services','Appointments / Bookings','fas fa-calendar-check',80,1,0,'Company appointment bookings and calendar flow'),
('deals','Deals / Services','Deals','fas fa-fire',90,1,0,'Deals, coupons, promotions and vouchers'),
('services','Deals / Services','Services','fas fa-concierge-bell',100,1,0,'Service packages and service booking options'),
('seo','Content / SEO','SEO','fas fa-search',110,1,0,'SEO pages, sitemap, redirects and share metadata'),
('notifications','Automation','Notifications','fas fa-bell',120,1,0,'In-app, email, SMS and WhatsApp notification controls'),
('ai_helper','Automation','AI Helper','fas fa-magic',130,0,0,'Optional AI listing/reply helper placeholder')
ON DUPLICATE KEY UPDATE
  module_group=VALUES(module_group),
  admin_icon=VALUES(admin_icon),
  sort_order=VALUES(sort_order),
  description=VALUES(description);

INSERT INTO mj_admin_dashboard_widgets(widget_key,widget_title,widget_group,is_enabled,sort_order) VALUES
('approvals','Approval Queue','Daily Operations',1,10),
('payments','Payment Proof Queue','Daily Operations',1,20),
('support','Open Support Tickets','Daily Operations',1,30),
('reports','Reports / Scam Queue','Trust & Safety',1,40),
('bookings','Booking Requests','Deals / Services',1,50),
('revenue','Finance Snapshot','Billing',1,60),
('health','System Health Score','System',1,70),
('recent_activity','Recent Activity Timeline','Overview',1,80)
ON DUPLICATE KEY UPDATE widget_title=VALUES(widget_title), widget_group=VALUES(widget_group), sort_order=VALUES(sort_order);



-- ============================================================
-- Source: install/migration_v2.9.0_advanced_admin_layout_billing_modal.sql
-- ============================================================

-- MJ Marketplace v2.9.0 Advanced Admin Layout Pro + Billing Payment Modal Fix
-- Safe to run once after uploading v2.9.0 files.

CREATE TABLE IF NOT EXISTS mj_admin_layout_settings (
  admin_id BIGINT NOT NULL PRIMARY KEY,
  layout_mode VARCHAR(40) NOT NULL DEFAULT 'comfortable',
  theme_mode VARCHAR(40) NOT NULL DEFAULT 'light',
  sidebar_default VARCHAR(40) NOT NULL DEFAULT 'expanded',
  fixed_header TINYINT(1) NOT NULL DEFAULT 1,
  card_shadow TINYINT(1) NOT NULL DEFAULT 1,
  dashboard_widgets TEXT NULL,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_admin_task_notes (
  id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
  admin_id BIGINT NULL,
  task_title VARCHAR(190) NOT NULL,
  task_status VARCHAR(50) NOT NULL DEFAULT 'open',
  task_priority VARCHAR(30) NOT NULL DEFAULT 'normal',
  related_type VARCHAR(80) NULL,
  related_id BIGINT NULL,
  due_at DATETIME NULL,
  note TEXT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  INDEX(admin_id),
  INDEX(task_status),
  INDEX(task_priority),
  INDEX(related_type, related_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_admin_task_notes (fresh installer safe).
CALL mj_safe_add_column('mj_admin_task_notes', 'admin_id', '`admin_id` BIGINT NULL');
CALL mj_safe_add_column('mj_admin_task_notes', 'task_title', '`task_title` VARCHAR(190) NOT NULL');
CALL mj_safe_add_column('mj_admin_task_notes', 'task_status', '`task_status` VARCHAR(50) NOT NULL DEFAULT ''open''');
CALL mj_safe_add_column('mj_admin_task_notes', 'task_priority', '`task_priority` VARCHAR(30) NOT NULL DEFAULT ''normal''');
CALL mj_safe_add_column('mj_admin_task_notes', 'related_type', '`related_type` VARCHAR(80) NULL');
CALL mj_safe_add_column('mj_admin_task_notes', 'related_id', '`related_id` BIGINT NULL');
CALL mj_safe_add_column('mj_admin_task_notes', 'due_at', '`due_at` DATETIME NULL');
CALL mj_safe_add_column('mj_admin_task_notes', 'note', '`note` TEXT NULL');
CALL mj_safe_add_column('mj_admin_task_notes', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');
CALL mj_safe_add_column('mj_admin_task_notes', 'updated_at', '`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');


-- Add missing task columns when upgrading from v2.8.9 table.
SET @dbname = DATABASE();
SET @col_exists := (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA=@dbname AND TABLE_NAME='mj_admin_task_notes' AND COLUMN_NAME='task_priority');
SET @sql := IF(@col_exists=0, 'ALTER TABLE mj_admin_task_notes ADD COLUMN task_priority VARCHAR(30) NOT NULL DEFAULT ''normal'' AFTER task_status', 'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @col_exists := (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA=@dbname AND TABLE_NAME='mj_admin_task_notes' AND COLUMN_NAME='related_type');
SET @sql := IF(@col_exists=0, 'ALTER TABLE mj_admin_task_notes ADD COLUMN related_type VARCHAR(80) NULL AFTER task_priority', 'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @col_exists := (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA=@dbname AND TABLE_NAME='mj_admin_task_notes' AND COLUMN_NAME='related_id');
SET @sql := IF(@col_exists=0, 'ALTER TABLE mj_admin_task_notes ADD COLUMN related_id BIGINT NULL AFTER related_type', 'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;

-- Requested default: featured listing and sponsored listing are each $10 for the default month.
INSERT INTO mj_settings(setting_key, setting_value) VALUES
('listing_upgrade_currency','SGD'),
('listing_upgrade_days','30'),
('listing_feature_charge_enabled','1'),
('listing_sponsor_charge_enabled','1'),
('listing_feature_charge_amount','10.00'),
('listing_sponsor_charge_amount','10.00')
ON DUPLICATE KEY UPDATE setting_value = CASE
  WHEN setting_key='listing_feature_charge_amount' AND setting_value IN ('','15','15.0','15.00','25','25.0','25.00') THEN VALUES(setting_value)
  WHEN setting_key='listing_sponsor_charge_amount' AND setting_value IN ('','15','15.0','15.00','25','25.0','25.00') THEN VALUES(setting_value)
  WHEN setting_key IN ('listing_upgrade_currency','listing_upgrade_days','listing_feature_charge_enabled','listing_sponsor_charge_enabled') AND setting_value='' THEN VALUES(setting_value)
  ELSE setting_value
END;



-- ============================================================
-- Source: install/migration_v2.9.1_bulk_payment_cart_invoice_checkout.sql
-- ============================================================

-- MJ Marketplace v2.9.1 Bulk Payment Cart + Invoice Checkout
-- Creates bulk payment batches, invoice items and receipt tables.
-- Existing data is preserved. PHP helper mj291_payment_ensure_schema() also adds missing columns safely.

CREATE TABLE IF NOT EXISTS mj_payment_batches (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  batch_no VARCHAR(80) NULL UNIQUE,
  invoice_id BIGINT NULL,
  user_id BIGINT NOT NULL,
  country_id BIGINT NULL,
  currency_code VARCHAR(10) NOT NULL DEFAULT 'SGD',
  subtotal_amount DECIMAL(12,2) NOT NULL DEFAULT 0,
  tax_amount DECIMAL(12,2) NOT NULL DEFAULT 0,
  total_amount DECIMAL(12,2) NOT NULL DEFAULT 0,
  payment_method VARCHAR(80) NULL,
  reference_no VARCHAR(190) NULL,
  proof_path VARCHAR(500) NULL,
  original_name VARCHAR(255) NULL,
  mime_type VARCHAR(120) NULL,
  file_size BIGINT NULL,
  note TEXT NULL,
  status VARCHAR(50) NOT NULL DEFAULT 'pending_review',
  admin_note TEXT NULL,
  reviewed_by BIGINT NULL,
  reviewed_at DATETIME NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  INDEX(user_id), INDEX(invoice_id), INDEX(status), INDEX(created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_payment_batches (fresh installer safe).
CALL mj_safe_add_column('mj_payment_batches', 'batch_no', '`batch_no` VARCHAR(80) NULL UNIQUE');
CALL mj_safe_add_column('mj_payment_batches', 'invoice_id', '`invoice_id` BIGINT NULL');
CALL mj_safe_add_column('mj_payment_batches', 'user_id', '`user_id` BIGINT NOT NULL');
CALL mj_safe_add_column('mj_payment_batches', 'country_id', '`country_id` BIGINT NULL');
CALL mj_safe_add_column('mj_payment_batches', 'currency_code', '`currency_code` VARCHAR(10) NOT NULL DEFAULT ''SGD''');
CALL mj_safe_add_column('mj_payment_batches', 'subtotal_amount', '`subtotal_amount` DECIMAL(12,2) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_payment_batches', 'tax_amount', '`tax_amount` DECIMAL(12,2) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_payment_batches', 'total_amount', '`total_amount` DECIMAL(12,2) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_payment_batches', 'payment_method', '`payment_method` VARCHAR(80) NULL');
CALL mj_safe_add_column('mj_payment_batches', 'reference_no', '`reference_no` VARCHAR(190) NULL');
CALL mj_safe_add_column('mj_payment_batches', 'proof_path', '`proof_path` VARCHAR(500) NULL');
CALL mj_safe_add_column('mj_payment_batches', 'original_name', '`original_name` VARCHAR(255) NULL');
CALL mj_safe_add_column('mj_payment_batches', 'mime_type', '`mime_type` VARCHAR(120) NULL');
CALL mj_safe_add_column('mj_payment_batches', 'file_size', '`file_size` BIGINT NULL');
CALL mj_safe_add_column('mj_payment_batches', 'note', '`note` TEXT NULL');
CALL mj_safe_add_column('mj_payment_batches', 'status', '`status` VARCHAR(50) NOT NULL DEFAULT ''pending_review''');
CALL mj_safe_add_column('mj_payment_batches', 'admin_note', '`admin_note` TEXT NULL');
CALL mj_safe_add_column('mj_payment_batches', 'reviewed_by', '`reviewed_by` BIGINT NULL');
CALL mj_safe_add_column('mj_payment_batches', 'reviewed_at', '`reviewed_at` DATETIME NULL');
CALL mj_safe_add_column('mj_payment_batches', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');
CALL mj_safe_add_column('mj_payment_batches', 'updated_at', '`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');


CREATE TABLE IF NOT EXISTS mj_payment_batch_items (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  batch_id BIGINT NOT NULL,
  user_id BIGINT NOT NULL,
  item_type VARCHAR(80) NOT NULL,
  item_id BIGINT NOT NULL DEFAULT 0,
  item_label VARCHAR(190) NULL,
  item_description TEXT NULL,
  listing_id BIGINT NULL,
  company_id BIGINT NULL,
  quantity INT NOT NULL DEFAULT 1,
  unit_amount DECIMAL(12,2) NOT NULL DEFAULT 0,
  total_amount DECIMAL(12,2) NOT NULL DEFAULT 0,
  currency_code VARCHAR(10) NOT NULL DEFAULT 'SGD',
  status VARCHAR(50) NOT NULL DEFAULT 'pending_review',
  activation_status VARCHAR(50) NOT NULL DEFAULT 'not_activated',
  admin_note TEXT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  INDEX(batch_id), INDEX(user_id), INDEX(item_type,item_id), INDEX(status), INDEX(activation_status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_payment_batch_items (fresh installer safe).
CALL mj_safe_add_column('mj_payment_batch_items', 'batch_id', '`batch_id` BIGINT NOT NULL');
CALL mj_safe_add_column('mj_payment_batch_items', 'user_id', '`user_id` BIGINT NOT NULL');
CALL mj_safe_add_column('mj_payment_batch_items', 'item_type', '`item_type` VARCHAR(80) NOT NULL');
CALL mj_safe_add_column('mj_payment_batch_items', 'item_id', '`item_id` BIGINT NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_payment_batch_items', 'item_label', '`item_label` VARCHAR(190) NULL');
CALL mj_safe_add_column('mj_payment_batch_items', 'item_description', '`item_description` TEXT NULL');
CALL mj_safe_add_column('mj_payment_batch_items', 'listing_id', '`listing_id` BIGINT NULL');
CALL mj_safe_add_column('mj_payment_batch_items', 'company_id', '`company_id` BIGINT NULL');
CALL mj_safe_add_column('mj_payment_batch_items', 'quantity', '`quantity` INT NOT NULL DEFAULT 1');
CALL mj_safe_add_column('mj_payment_batch_items', 'unit_amount', '`unit_amount` DECIMAL(12,2) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_payment_batch_items', 'total_amount', '`total_amount` DECIMAL(12,2) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_payment_batch_items', 'currency_code', '`currency_code` VARCHAR(10) NOT NULL DEFAULT ''SGD''');
CALL mj_safe_add_column('mj_payment_batch_items', 'status', '`status` VARCHAR(50) NOT NULL DEFAULT ''pending_review''');
CALL mj_safe_add_column('mj_payment_batch_items', 'activation_status', '`activation_status` VARCHAR(50) NOT NULL DEFAULT ''not_activated''');
CALL mj_safe_add_column('mj_payment_batch_items', 'admin_note', '`admin_note` TEXT NULL');
CALL mj_safe_add_column('mj_payment_batch_items', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');
CALL mj_safe_add_column('mj_payment_batch_items', 'updated_at', '`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');


CREATE TABLE IF NOT EXISTS mj_invoice_items (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  invoice_id BIGINT NOT NULL,
  user_id BIGINT NOT NULL,
  item_type VARCHAR(80) NULL,
  item_id BIGINT NULL,
  description TEXT NULL,
  quantity INT NOT NULL DEFAULT 1,
  unit_amount DECIMAL(12,2) NOT NULL DEFAULT 0,
  total_amount DECIMAL(12,2) NOT NULL DEFAULT 0,
  currency_code VARCHAR(10) NOT NULL DEFAULT 'SGD',
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX(invoice_id), INDEX(user_id), INDEX(item_type,item_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_receipts (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  receipt_no VARCHAR(80) NULL UNIQUE,
  invoice_id BIGINT NULL,
  batch_id BIGINT NULL,
  user_id BIGINT NOT NULL,
  amount DECIMAL(12,2) NOT NULL DEFAULT 0,
  currency_code VARCHAR(10) NOT NULL DEFAULT 'SGD',
  payment_method VARCHAR(80) NULL,
  reference_no VARCHAR(190) NULL,
  status VARCHAR(40) NOT NULL DEFAULT 'paid',
  admin_id BIGINT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX(invoice_id), INDEX(batch_id), INDEX(user_id), INDEX(receipt_no)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- Optional safe ALTER lines. If your MySQL says duplicate column, skip that line.
-- ALTER TABLE mj_invoices ADD COLUMN batch_id BIGINT NULL;
-- ALTER TABLE mj_invoices ADD COLUMN proof_path VARCHAR(500) NULL;
-- ALTER TABLE mj_invoices ADD COLUMN payment_method VARCHAR(80) NULL;
-- ALTER TABLE mj_invoices ADD COLUMN reference_no VARCHAR(190) NULL;
-- ALTER TABLE mj_invoices ADD COLUMN admin_note TEXT NULL;
-- ALTER TABLE mj_listing_renewal_requests ADD COLUMN batch_id BIGINT NULL;
-- ALTER TABLE mj_listing_renewal_requests ADD COLUMN invoice_id BIGINT NULL;
-- ALTER TABLE mj_listing_upgrade_requests ADD COLUMN batch_id BIGINT NULL;
-- ALTER TABLE mj_listing_upgrade_requests ADD COLUMN invoice_id BIGINT NULL;
-- ALTER TABLE mj_user_limit_upgrade_requests ADD COLUMN batch_id BIGINT NULL;
-- ALTER TABLE mj_user_limit_upgrade_requests ADD COLUMN invoice_id BIGINT NULL;
-- ALTER TABLE mj_user_limit_upgrade_requests ADD COLUMN approved_slots INT NULL;
-- ALTER TABLE mj_user_limit_upgrade_requests ADD COLUMN unit_amount DECIMAL(12,2) NULL;

INSERT INTO mj_settings(setting_key,setting_value) VALUES
('payment_cart_enabled','1'),
('payment_cart_allow_partial_admin_approval','1'),
('payment_cart_tax_enabled','0'),
('payment_cart_tax_label','GST/VAT'),
('payment_cart_default_tax_rate','0'),
('extra_listing_slot_charge_amount','5.00'),
('extra_company_slot_charge_amount','5.00'),
('extra_slot_currency','SGD'),
('extra_slot_min_qty','1'),
('extra_slot_max_qty','100'),
('listing_feature_charge_amount','10.00'),
('listing_sponsor_charge_amount','10.00'),
('listing_upgrade_days','30')
ON DUPLICATE KEY UPDATE setting_value=setting_value;



-- ============================================================
-- Source: install/migration_v2.9.2_invoice_first_editable_checkout_layout.sql
-- ============================================================

-- MJ Marketplace v2.9.2 Invoice First + Editable Checkout + Wider Layout
-- No destructive changes. v2.9.1 tables are reused.
-- This migration only documents the new invoice-first statuses and keeps payment settings available.

INSERT INTO mj_settings(setting_key,setting_value) VALUES
('payment_cart_invoice_first','1'),
('payment_cart_allow_edit_before_proof','1'),
('payment_cart_label_generate_invoice','Generate Invoice'),
('payment_cart_label_submit_proof','Submit Payment Proof')
ON DUPLICATE KEY UPDATE setting_value=setting_value;

-- Status flow used by v2.9.2:
-- invoice_created  = user generated invoice but has not uploaded proof yet
-- pending_review   = user uploaded proof and admin must review
-- need_more_info   = admin asked user to re-upload or clarify
-- rejected         = admin rejected proof
-- approved/paid    = admin approved and service activated



-- ============================================================
-- Source: install/migration_v2.9.3_payment_speed_finance_tally.sql
-- ============================================================

-- MJ Marketplace v2.9.3 Payment Speed + Finance Tally Fix
-- Safe migration: adds fast indexes only when required columns exist. No destructive changes.

INSERT INTO mj_settings(setting_key,setting_value) VALUES
('v293_payment_performance_schema_ready','1'),
('payment_finance_tally_source','payment_batches'),
('payment_fast_billing_pages','1')
ON DUPLICATE KEY UPDATE setting_value=VALUES(setting_value);

DROP PROCEDURE IF EXISTS mj_v293_add_index_if_missing;
DELIMITER $$
CREATE PROCEDURE mj_v293_add_index_if_missing(
    IN p_table VARCHAR(100),
    IN p_index VARCHAR(100),
    IN p_sql TEXT,
    IN p_col1 VARCHAR(100),
    IN p_col2 VARCHAR(100),
    IN p_col3 VARCHAR(100)
)
BEGIN
    IF EXISTS (SELECT 1 FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = p_table)
       AND NOT EXISTS (SELECT 1 FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = p_table AND INDEX_NAME = p_index)
       AND (p_col1 = '' OR EXISTS (SELECT 1 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = p_table AND COLUMN_NAME = p_col1))
       AND (p_col2 = '' OR EXISTS (SELECT 1 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = p_table AND COLUMN_NAME = p_col2))
       AND (p_col3 = '' OR EXISTS (SELECT 1 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = p_table AND COLUMN_NAME = p_col3)) THEN
        SET @s = p_sql;
        PREPARE stmt FROM @s;
        EXECUTE stmt;
        DEALLOCATE PREPARE stmt;
    END IF;
END$$
DELIMITER ;

CALL mj_v293_add_index_if_missing('mj_payment_batches','idx_v293_user_status_created','CREATE INDEX idx_v293_user_status_created ON mj_payment_batches(user_id,status,created_at)','user_id','status','created_at');
CALL mj_v293_add_index_if_missing('mj_payment_batches','idx_v293_status_created','CREATE INDEX idx_v293_status_created ON mj_payment_batches(status,created_at)','status','created_at','');
CALL mj_v293_add_index_if_missing('mj_payment_batches','idx_v293_invoice_status','CREATE INDEX idx_v293_invoice_status ON mj_payment_batches(invoice_id,status)','invoice_id','status','');
CALL mj_v293_add_index_if_missing('mj_payment_batch_items','idx_v293_batch_user_status','CREATE INDEX idx_v293_batch_user_status ON mj_payment_batch_items(batch_id,user_id,status)','batch_id','user_id','status');
CALL mj_v293_add_index_if_missing('mj_invoices','idx_v293_user_status_created','CREATE INDEX idx_v293_user_status_created ON mj_invoices(user_id,status,created_at)','user_id','status','created_at');
CALL mj_v293_add_index_if_missing('mj_invoices','idx_v293_status_created','CREATE INDEX idx_v293_status_created ON mj_invoices(status,created_at)','status','created_at','');
CALL mj_v293_add_index_if_missing('mj_invoices','idx_v293_batch_status','CREATE INDEX idx_v293_batch_status ON mj_invoices(batch_id,status)','batch_id','status','');
CALL mj_v293_add_index_if_missing('mj_receipts','idx_v293_user_created','CREATE INDEX idx_v293_user_created ON mj_receipts(user_id,created_at)','user_id','created_at','');
CALL mj_v293_add_index_if_missing('mj_upgrade_payment_proofs','idx_v293_status_batch_created','CREATE INDEX idx_v293_status_batch_created ON mj_upgrade_payment_proofs(status,batch_id,created_at)','status','batch_id','created_at');
CALL mj_v293_add_index_if_missing('mj_payment_proofs','idx_v293_status_created','CREATE INDEX idx_v293_status_created ON mj_payment_proofs(status,uploaded_at)','status','uploaded_at','');
CALL mj_v293_add_index_if_missing('mj_order_payment_proofs','idx_v293_status_created','CREATE INDEX idx_v293_status_created ON mj_order_payment_proofs(status,created_at)','status','created_at','');

DROP PROCEDURE IF EXISTS mj_v293_add_index_if_missing;



-- ============================================================
-- Source: install/migration_v2.9.4_admin_one_payment_center.sql
-- ============================================================

-- MJ Marketplace v2.9.4 Admin One Payment Center
-- Safe index migration. Run once after upload. Missing tables are skipped.
SET @db := DATABASE();

SET @sql := (SELECT IF((SELECT COUNT(*) FROM information_schema.TABLES WHERE table_schema=@db AND table_name='mj_payment_batches')>0 AND (SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema=@db AND table_name='mj_payment_batches' AND index_name='idx_mj_payment_batches_status_updated')=0, 'ALTER TABLE mj_payment_batches ADD INDEX idx_mj_payment_batches_status_updated (status, updated_at)', 'SELECT 1'));
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;

SET @sql := (SELECT IF((SELECT COUNT(*) FROM information_schema.TABLES WHERE table_schema=@db AND table_name='mj_payment_batches')>0 AND (SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema=@db AND table_name='mj_payment_batches' AND index_name='idx_mj_payment_batches_user_status')=0, 'ALTER TABLE mj_payment_batches ADD INDEX idx_mj_payment_batches_user_status (user_id, status)', 'SELECT 1'));
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;

SET @sql := (SELECT IF((SELECT COUNT(*) FROM information_schema.TABLES WHERE table_schema=@db AND table_name='mj_payment_batches')>0 AND (SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema=@db AND table_name='mj_payment_batches' AND index_name='idx_mj_payment_batches_invoice_status')=0, 'ALTER TABLE mj_payment_batches ADD INDEX idx_mj_payment_batches_invoice_status (invoice_id, status)', 'SELECT 1'));
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;

SET @sql := (SELECT IF((SELECT COUNT(*) FROM information_schema.TABLES WHERE table_schema=@db AND table_name='mj_payment_batch_items')>0 AND (SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema=@db AND table_name='mj_payment_batch_items' AND index_name='idx_mj_payment_batch_items_batch_status')=0, 'ALTER TABLE mj_payment_batch_items ADD INDEX idx_mj_payment_batch_items_batch_status (batch_id, status)', 'SELECT 1'));
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;

SET @sql := (SELECT IF((SELECT COUNT(*) FROM information_schema.TABLES WHERE table_schema=@db AND table_name='mj_invoices')>0 AND (SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema=@db AND table_name='mj_invoices' AND index_name='idx_mj_invoices_status_created')=0, 'ALTER TABLE mj_invoices ADD INDEX idx_mj_invoices_status_created (status, created_at)', 'SELECT 1'));
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;

SET @sql := (SELECT IF((SELECT COUNT(*) FROM information_schema.TABLES WHERE table_schema=@db AND table_name='mj_invoices')>0 AND (SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema=@db AND table_name='mj_invoices' AND index_name='idx_mj_invoices_user_status')=0, 'ALTER TABLE mj_invoices ADD INDEX idx_mj_invoices_user_status (user_id, status)', 'SELECT 1'));
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;

SET @sql := (SELECT IF((SELECT COUNT(*) FROM information_schema.TABLES WHERE table_schema=@db AND table_name='mj_upgrade_payment_proofs')>0 AND (SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema=@db AND table_name='mj_upgrade_payment_proofs' AND index_name='idx_mj_upgrade_payment_proofs_status_created')=0, 'ALTER TABLE mj_upgrade_payment_proofs ADD INDEX idx_mj_upgrade_payment_proofs_status_created (status, created_at)', 'SELECT 1'));
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;

SET @sql := (SELECT IF((SELECT COUNT(*) FROM information_schema.TABLES WHERE table_schema=@db AND table_name='mj_payment_proofs')>0 AND (SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema=@db AND table_name='mj_payment_proofs' AND index_name='idx_mj_payment_proofs_status_uploaded')=0, 'ALTER TABLE mj_payment_proofs ADD INDEX idx_mj_payment_proofs_status_uploaded (status, uploaded_at)', 'SELECT 1'));
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;

SET @sql := (SELECT IF((SELECT COUNT(*) FROM information_schema.TABLES WHERE table_schema=@db AND table_name='mj_order_payment_proofs')>0 AND (SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema=@db AND table_name='mj_order_payment_proofs' AND index_name='idx_mj_order_payment_proofs_status_created')=0, 'ALTER TABLE mj_order_payment_proofs ADD INDEX idx_mj_order_payment_proofs_status_created (status, created_at)', 'SELECT 1'));
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;



-- ============================================================
-- Source: install/migration_v2.9.5_wallet_payment_center_pro.sql
-- ============================================================

-- MJ Marketplace v2.9.5 Wallet + Payment Center Pro
-- Safe migration. Run once after upload. No existing data is deleted.

CREATE TABLE IF NOT EXISTS mj_wallets (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  currency_code VARCHAR(10) NOT NULL DEFAULT 'SGD',
  balance DECIMAL(12,2) NOT NULL DEFAULT 0,
  held_amount DECIMAL(12,2) NOT NULL DEFAULT 0,
  status VARCHAR(40) NOT NULL DEFAULT 'active',
  admin_note TEXT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  UNIQUE KEY uniq_user_currency (user_id,currency_code),
  INDEX idx_mj_wallets_user (user_id),
  INDEX idx_mj_wallets_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_wallets (fresh installer safe).
CALL mj_safe_add_column('mj_wallets', 'user_id', '`user_id` BIGINT NOT NULL');
CALL mj_safe_add_column('mj_wallets', 'currency_code', '`currency_code` VARCHAR(10) NOT NULL DEFAULT ''SGD''');
CALL mj_safe_add_column('mj_wallets', 'balance', '`balance` DECIMAL(12,2) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_wallets', 'held_amount', '`held_amount` DECIMAL(12,2) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_wallets', 'status', '`status` VARCHAR(40) NOT NULL DEFAULT ''active''');
CALL mj_safe_add_column('mj_wallets', 'admin_note', '`admin_note` TEXT NULL');
CALL mj_safe_add_column('mj_wallets', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');
CALL mj_safe_add_column('mj_wallets', 'updated_at', '`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');


CREATE TABLE IF NOT EXISTS mj_wallet_transactions (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  wallet_id BIGINT NULL,
  user_id BIGINT NOT NULL,
  currency_code VARCHAR(10) NOT NULL DEFAULT 'SGD',
  transaction_type VARCHAR(60) NOT NULL,
  direction VARCHAR(20) NOT NULL DEFAULT 'credit',
  amount DECIMAL(12,2) NOT NULL DEFAULT 0,
  balance_before DECIMAL(12,2) NOT NULL DEFAULT 0,
  balance_after DECIMAL(12,2) NOT NULL DEFAULT 0,
  held_before DECIMAL(12,2) NOT NULL DEFAULT 0,
  held_after DECIMAL(12,2) NOT NULL DEFAULT 0,
  description TEXT NULL,
  source_type VARCHAR(80) NULL,
  source_id BIGINT NULL,
  invoice_id BIGINT NULL,
  batch_id BIGINT NULL,
  admin_id BIGINT NULL,
  admin_note TEXT NULL,
  ip_address VARCHAR(80) NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_mj_wallet_tx_wallet (wallet_id),
  INDEX idx_mj_wallet_tx_user (user_id),
  INDEX idx_mj_wallet_tx_type (transaction_type),
  INDEX idx_mj_wallet_tx_batch (batch_id),
  INDEX idx_mj_wallet_tx_invoice (invoice_id),
  INDEX idx_mj_wallet_tx_created (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_wallet_transactions (fresh installer safe).
CALL mj_safe_add_column('mj_wallet_transactions', 'wallet_id', '`wallet_id` BIGINT NULL');
CALL mj_safe_add_column('mj_wallet_transactions', 'user_id', '`user_id` BIGINT NOT NULL');
CALL mj_safe_add_column('mj_wallet_transactions', 'currency_code', '`currency_code` VARCHAR(10) NOT NULL DEFAULT ''SGD''');
CALL mj_safe_add_column('mj_wallet_transactions', 'transaction_type', '`transaction_type` VARCHAR(60) NOT NULL');
CALL mj_safe_add_column('mj_wallet_transactions', 'direction', '`direction` VARCHAR(20) NOT NULL DEFAULT ''credit''');
CALL mj_safe_add_column('mj_wallet_transactions', 'amount', '`amount` DECIMAL(12,2) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_wallet_transactions', 'balance_before', '`balance_before` DECIMAL(12,2) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_wallet_transactions', 'balance_after', '`balance_after` DECIMAL(12,2) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_wallet_transactions', 'held_before', '`held_before` DECIMAL(12,2) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_wallet_transactions', 'held_after', '`held_after` DECIMAL(12,2) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_wallet_transactions', 'description', '`description` TEXT NULL');
CALL mj_safe_add_column('mj_wallet_transactions', 'source_type', '`source_type` VARCHAR(80) NULL');
CALL mj_safe_add_column('mj_wallet_transactions', 'source_id', '`source_id` BIGINT NULL');
CALL mj_safe_add_column('mj_wallet_transactions', 'invoice_id', '`invoice_id` BIGINT NULL');
CALL mj_safe_add_column('mj_wallet_transactions', 'batch_id', '`batch_id` BIGINT NULL');
CALL mj_safe_add_column('mj_wallet_transactions', 'admin_id', '`admin_id` BIGINT NULL');
CALL mj_safe_add_column('mj_wallet_transactions', 'admin_note', '`admin_note` TEXT NULL');
CALL mj_safe_add_column('mj_wallet_transactions', 'ip_address', '`ip_address` VARCHAR(80) NULL');
CALL mj_safe_add_column('mj_wallet_transactions', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');


CREATE TABLE IF NOT EXISTS mj_wallet_holds (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  wallet_id BIGINT NOT NULL,
  user_id BIGINT NOT NULL,
  batch_id BIGINT NULL,
  invoice_id BIGINT NULL,
  amount DECIMAL(12,2) NOT NULL DEFAULT 0,
  currency_code VARCHAR(10) NOT NULL DEFAULT 'SGD',
  status VARCHAR(40) NOT NULL DEFAULT 'held',
  note TEXT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  released_at DATETIME NULL,
  captured_at DATETIME NULL,
  INDEX idx_mj_wallet_holds_wallet (wallet_id),
  INDEX idx_mj_wallet_holds_user (user_id),
  INDEX idx_mj_wallet_holds_batch (batch_id),
  INDEX idx_mj_wallet_holds_invoice (invoice_id),
  INDEX idx_mj_wallet_holds_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

INSERT INTO mj_settings(setting_key,setting_value) VALUES
('wallet_enabled','1'),
('wallet_payment_enabled','1'),
('wallet_payment_admin_approval_required','1'),
('wallet_low_balance_amount','10.00'),
('wallet_default_currency','SGD')
ON DUPLICATE KEY UPDATE setting_value=setting_value;



-- ============================================================
-- Source: install/migration_v2.9.8_wallet_search_schema_compatibility.sql
-- ============================================================

-- MJ Marketplace v2.9.8 Wallet Search + Schema Compatibility Fix
-- Run once. Safe for old wallet tables; it only adds missing columns/indexes.

CALL mj_safe_add_column('mj_wallets', 'currency_code', 'currency_code VARCHAR(10) NOT NULL DEFAULT ''SGD''');
CALL mj_safe_add_column('mj_wallets', 'balance', 'balance DECIMAL(12,2) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_wallets', 'held_amount', 'held_amount DECIMAL(12,2) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_wallets', 'status', 'status VARCHAR(40) NOT NULL DEFAULT ''active''');
CALL mj_safe_add_column('mj_wallets', 'admin_note', 'admin_note TEXT NULL');
CALL mj_safe_add_column('mj_wallets', 'created_at', 'created_at DATETIME DEFAULT CURRENT_TIMESTAMP');
CALL mj_safe_add_column('mj_wallets', 'updated_at', 'updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');

CALL mj_safe_add_column('mj_wallet_transactions', 'wallet_id', 'wallet_id BIGINT NULL');
CALL mj_safe_add_column('mj_wallet_transactions', 'user_id', 'user_id BIGINT NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_wallet_transactions', 'currency_code', 'currency_code VARCHAR(10) NOT NULL DEFAULT ''SGD''');
CALL mj_safe_add_column('mj_wallet_transactions', 'transaction_type', 'transaction_type VARCHAR(60) NOT NULL DEFAULT ''adjustment''');
CALL mj_safe_add_column('mj_wallet_transactions', 'direction', 'direction VARCHAR(20) NOT NULL DEFAULT ''credit''');
CALL mj_safe_add_column('mj_wallet_transactions', 'balance_before', 'balance_before DECIMAL(12,2) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_wallet_transactions', 'balance_after', 'balance_after DECIMAL(12,2) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_wallet_transactions', 'held_before', 'held_before DECIMAL(12,2) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_wallet_transactions', 'held_after', 'held_after DECIMAL(12,2) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_wallet_transactions', 'description', 'description TEXT NULL');
CALL mj_safe_add_column('mj_wallet_transactions', 'source_type', 'source_type VARCHAR(80) NULL');
CALL mj_safe_add_column('mj_wallet_transactions', 'source_id', 'source_id BIGINT NULL');
CALL mj_safe_add_column('mj_wallet_transactions', 'invoice_id', 'invoice_id BIGINT NULL');
CALL mj_safe_add_column('mj_wallet_transactions', 'batch_id', 'batch_id BIGINT NULL');
CALL mj_safe_add_column('mj_wallet_transactions', 'admin_id', 'admin_id BIGINT NULL');
CALL mj_safe_add_column('mj_wallet_transactions', 'admin_note', 'admin_note TEXT NULL');
CALL mj_safe_add_column('mj_wallet_transactions', 'ip_address', 'ip_address VARCHAR(80) NULL');

-- Optional indexes. Ignore if your MySQL version does not support IF NOT EXISTS for indexes.
CALL mj_safe_add_index('mj_wallets', 'idx_wallet_user_currency', 'ADD INDEX idx_wallet_user_currency (user_id, currency_code)');
CALL mj_safe_add_index('mj_wallet_transactions', 'idx_wallet_tx_user_created', 'ADD INDEX idx_wallet_tx_user_created (user_id, created_at)');



-- ============================================================
-- Source: install/migration_v2.9.10_wallet_direct_approve_user_topup.sql
-- ============================================================

-- MJ Marketplace v2.9.10 Wallet Direct Approval + User Wallet Top-up
-- Run once after uploading v2.9.10 files.

INSERT INTO mj_settings(setting_key, setting_value)
VALUES
('wallet_enabled','1'),
('wallet_payment_enabled','1'),
('wallet_payment_admin_approval_required','0'),
('wallet_topup_enabled','1'),
('wallet_topup_min_amount','1.00'),
('wallet_topup_max_amount','9999.00'),
('wallet_default_currency','SGD')
ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value);

-- Keep wallet schema compatible with older installs.
CREATE TABLE IF NOT EXISTS mj_wallets (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  currency_code VARCHAR(10) NOT NULL DEFAULT 'SGD',
  balance DECIMAL(12,2) NOT NULL DEFAULT 0,
  held_amount DECIMAL(12,2) NOT NULL DEFAULT 0,
  status VARCHAR(40) NOT NULL DEFAULT 'active',
  admin_note TEXT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  UNIQUE KEY uniq_user_currency (user_id,currency_code),
  INDEX(user_id), INDEX(status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_wallets (fresh installer safe).
CALL mj_safe_add_column('mj_wallets', 'user_id', '`user_id` BIGINT NOT NULL');
CALL mj_safe_add_column('mj_wallets', 'currency_code', '`currency_code` VARCHAR(10) NOT NULL DEFAULT ''SGD''');
CALL mj_safe_add_column('mj_wallets', 'balance', '`balance` DECIMAL(12,2) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_wallets', 'held_amount', '`held_amount` DECIMAL(12,2) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_wallets', 'status', '`status` VARCHAR(40) NOT NULL DEFAULT ''active''');
CALL mj_safe_add_column('mj_wallets', 'admin_note', '`admin_note` TEXT NULL');
CALL mj_safe_add_column('mj_wallets', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');
CALL mj_safe_add_column('mj_wallets', 'updated_at', '`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');


CREATE TABLE IF NOT EXISTS mj_wallet_transactions (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  wallet_id BIGINT NULL,
  user_id BIGINT NOT NULL,
  currency_code VARCHAR(10) NOT NULL DEFAULT 'SGD',
  transaction_type VARCHAR(60) NOT NULL,
  direction VARCHAR(20) NOT NULL DEFAULT 'credit',
  amount DECIMAL(12,2) NOT NULL DEFAULT 0,
  balance_before DECIMAL(12,2) NOT NULL DEFAULT 0,
  balance_after DECIMAL(12,2) NOT NULL DEFAULT 0,
  held_before DECIMAL(12,2) NOT NULL DEFAULT 0,
  held_after DECIMAL(12,2) NOT NULL DEFAULT 0,
  description TEXT NULL,
  source_type VARCHAR(80) NULL,
  source_id BIGINT NULL,
  invoice_id BIGINT NULL,
  batch_id BIGINT NULL,
  admin_id BIGINT NULL,
  admin_note TEXT NULL,
  ip_address VARCHAR(80) NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX(wallet_id), INDEX(user_id), INDEX(transaction_type), INDEX(batch_id), INDEX(invoice_id), INDEX(created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_wallet_transactions (fresh installer safe).
CALL mj_safe_add_column('mj_wallet_transactions', 'wallet_id', '`wallet_id` BIGINT NULL');
CALL mj_safe_add_column('mj_wallet_transactions', 'user_id', '`user_id` BIGINT NOT NULL');
CALL mj_safe_add_column('mj_wallet_transactions', 'currency_code', '`currency_code` VARCHAR(10) NOT NULL DEFAULT ''SGD''');
CALL mj_safe_add_column('mj_wallet_transactions', 'transaction_type', '`transaction_type` VARCHAR(60) NOT NULL');
CALL mj_safe_add_column('mj_wallet_transactions', 'direction', '`direction` VARCHAR(20) NOT NULL DEFAULT ''credit''');
CALL mj_safe_add_column('mj_wallet_transactions', 'amount', '`amount` DECIMAL(12,2) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_wallet_transactions', 'balance_before', '`balance_before` DECIMAL(12,2) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_wallet_transactions', 'balance_after', '`balance_after` DECIMAL(12,2) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_wallet_transactions', 'held_before', '`held_before` DECIMAL(12,2) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_wallet_transactions', 'held_after', '`held_after` DECIMAL(12,2) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_wallet_transactions', 'description', '`description` TEXT NULL');
CALL mj_safe_add_column('mj_wallet_transactions', 'source_type', '`source_type` VARCHAR(80) NULL');
CALL mj_safe_add_column('mj_wallet_transactions', 'source_id', '`source_id` BIGINT NULL');
CALL mj_safe_add_column('mj_wallet_transactions', 'invoice_id', '`invoice_id` BIGINT NULL');
CALL mj_safe_add_column('mj_wallet_transactions', 'batch_id', '`batch_id` BIGINT NULL');
CALL mj_safe_add_column('mj_wallet_transactions', 'admin_id', '`admin_id` BIGINT NULL');
CALL mj_safe_add_column('mj_wallet_transactions', 'admin_note', '`admin_note` TEXT NULL');
CALL mj_safe_add_column('mj_wallet_transactions', 'ip_address', '`ip_address` VARCHAR(80) NULL');
CALL mj_safe_add_column('mj_wallet_transactions', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');


CREATE TABLE IF NOT EXISTS mj_wallet_holds (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  wallet_id BIGINT NOT NULL,
  user_id BIGINT NOT NULL,
  batch_id BIGINT NULL,
  invoice_id BIGINT NULL,
  amount DECIMAL(12,2) NOT NULL DEFAULT 0,
  currency_code VARCHAR(10) NOT NULL DEFAULT 'SGD',
  status VARCHAR(40) NOT NULL DEFAULT 'held',
  note TEXT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  released_at DATETIME NULL,
  captured_at DATETIME NULL,
  INDEX(wallet_id), INDEX(user_id), INDEX(batch_id), INDEX(invoice_id), INDEX(status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_wallet_holds (fresh installer safe).
CALL mj_safe_add_column('mj_wallet_holds', 'wallet_id', '`wallet_id` BIGINT NOT NULL');
CALL mj_safe_add_column('mj_wallet_holds', 'user_id', '`user_id` BIGINT NOT NULL');
CALL mj_safe_add_column('mj_wallet_holds', 'batch_id', '`batch_id` BIGINT NULL');
CALL mj_safe_add_column('mj_wallet_holds', 'invoice_id', '`invoice_id` BIGINT NULL');
CALL mj_safe_add_column('mj_wallet_holds', 'amount', '`amount` DECIMAL(12,2) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_wallet_holds', 'currency_code', '`currency_code` VARCHAR(10) NOT NULL DEFAULT ''SGD''');
CALL mj_safe_add_column('mj_wallet_holds', 'status', '`status` VARCHAR(40) NOT NULL DEFAULT ''held''');
CALL mj_safe_add_column('mj_wallet_holds', 'note', '`note` TEXT NULL');
CALL mj_safe_add_column('mj_wallet_holds', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');
CALL mj_safe_add_column('mj_wallet_holds', 'released_at', '`released_at` DATETIME NULL');
CALL mj_safe_add_column('mj_wallet_holds', 'captured_at', '`captured_at` DATETIME NULL');




-- ============================================================
-- Source: install/migration_v2.9.11_payment_speed_finance_tally_hardening.sql
-- ============================================================

-- MJ Marketplace v2.9.11 Payment Speed + Finance Tally Hardening
-- Run once after uploading v2.9.11 files.
-- Safe settings only; table/index changes are handled defensively by PHP helpers during normal page load.

INSERT INTO mj_settings(setting_key, setting_value)
VALUES
('wallet_enabled','1'),
('wallet_payment_enabled','1'),
('wallet_payment_admin_approval_required','0'),
('wallet_topup_enabled','1'),
('wallet_topup_min_amount','1.00'),
('wallet_topup_max_amount','9999.00'),
('wallet_default_currency','SGD'),
('v2911_payment_idempotency_ready','1'),
('v2911_finance_tally_ready','1')
ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value);

-- Wallet schema compatibility for older installs.
CREATE TABLE IF NOT EXISTS mj_wallets (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  currency_code VARCHAR(10) NOT NULL DEFAULT 'SGD',
  balance DECIMAL(12,2) NOT NULL DEFAULT 0,
  held_amount DECIMAL(12,2) NOT NULL DEFAULT 0,
  status VARCHAR(40) NOT NULL DEFAULT 'active',
  admin_note TEXT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  UNIQUE KEY uniq_user_currency (user_id,currency_code),
  INDEX(user_id), INDEX(status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_wallets (fresh installer safe).
CALL mj_safe_add_column('mj_wallets', 'user_id', '`user_id` BIGINT NOT NULL');
CALL mj_safe_add_column('mj_wallets', 'currency_code', '`currency_code` VARCHAR(10) NOT NULL DEFAULT ''SGD''');
CALL mj_safe_add_column('mj_wallets', 'balance', '`balance` DECIMAL(12,2) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_wallets', 'held_amount', '`held_amount` DECIMAL(12,2) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_wallets', 'status', '`status` VARCHAR(40) NOT NULL DEFAULT ''active''');
CALL mj_safe_add_column('mj_wallets', 'admin_note', '`admin_note` TEXT NULL');
CALL mj_safe_add_column('mj_wallets', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');
CALL mj_safe_add_column('mj_wallets', 'updated_at', '`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');


CREATE TABLE IF NOT EXISTS mj_wallet_transactions (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  wallet_id BIGINT NULL,
  user_id BIGINT NOT NULL,
  currency_code VARCHAR(10) NOT NULL DEFAULT 'SGD',
  transaction_type VARCHAR(60) NOT NULL,
  direction VARCHAR(20) NOT NULL DEFAULT 'credit',
  amount DECIMAL(12,2) NOT NULL DEFAULT 0,
  balance_before DECIMAL(12,2) NOT NULL DEFAULT 0,
  balance_after DECIMAL(12,2) NOT NULL DEFAULT 0,
  held_before DECIMAL(12,2) NOT NULL DEFAULT 0,
  held_after DECIMAL(12,2) NOT NULL DEFAULT 0,
  description TEXT NULL,
  source_type VARCHAR(80) NULL,
  source_id BIGINT NULL,
  invoice_id BIGINT NULL,
  batch_id BIGINT NULL,
  admin_id BIGINT NULL,
  admin_note TEXT NULL,
  ip_address VARCHAR(80) NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX(wallet_id), INDEX(user_id), INDEX(transaction_type), INDEX(batch_id), INDEX(invoice_id), INDEX(created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_wallet_transactions (fresh installer safe).
CALL mj_safe_add_column('mj_wallet_transactions', 'wallet_id', '`wallet_id` BIGINT NULL');
CALL mj_safe_add_column('mj_wallet_transactions', 'user_id', '`user_id` BIGINT NOT NULL');
CALL mj_safe_add_column('mj_wallet_transactions', 'currency_code', '`currency_code` VARCHAR(10) NOT NULL DEFAULT ''SGD''');
CALL mj_safe_add_column('mj_wallet_transactions', 'transaction_type', '`transaction_type` VARCHAR(60) NOT NULL');
CALL mj_safe_add_column('mj_wallet_transactions', 'direction', '`direction` VARCHAR(20) NOT NULL DEFAULT ''credit''');
CALL mj_safe_add_column('mj_wallet_transactions', 'amount', '`amount` DECIMAL(12,2) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_wallet_transactions', 'balance_before', '`balance_before` DECIMAL(12,2) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_wallet_transactions', 'balance_after', '`balance_after` DECIMAL(12,2) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_wallet_transactions', 'held_before', '`held_before` DECIMAL(12,2) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_wallet_transactions', 'held_after', '`held_after` DECIMAL(12,2) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_wallet_transactions', 'description', '`description` TEXT NULL');
CALL mj_safe_add_column('mj_wallet_transactions', 'source_type', '`source_type` VARCHAR(80) NULL');
CALL mj_safe_add_column('mj_wallet_transactions', 'source_id', '`source_id` BIGINT NULL');
CALL mj_safe_add_column('mj_wallet_transactions', 'invoice_id', '`invoice_id` BIGINT NULL');
CALL mj_safe_add_column('mj_wallet_transactions', 'batch_id', '`batch_id` BIGINT NULL');
CALL mj_safe_add_column('mj_wallet_transactions', 'admin_id', '`admin_id` BIGINT NULL');
CALL mj_safe_add_column('mj_wallet_transactions', 'admin_note', '`admin_note` TEXT NULL');
CALL mj_safe_add_column('mj_wallet_transactions', 'ip_address', '`ip_address` VARCHAR(80) NULL');
CALL mj_safe_add_column('mj_wallet_transactions', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');


CREATE TABLE IF NOT EXISTS mj_wallet_holds (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  wallet_id BIGINT NOT NULL,
  user_id BIGINT NOT NULL,
  batch_id BIGINT NULL,
  invoice_id BIGINT NULL,
  amount DECIMAL(12,2) NOT NULL DEFAULT 0,
  currency_code VARCHAR(10) NOT NULL DEFAULT 'SGD',
  status VARCHAR(40) NOT NULL DEFAULT 'held',
  note TEXT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  released_at DATETIME NULL,
  captured_at DATETIME NULL,
  INDEX(wallet_id), INDEX(user_id), INDEX(batch_id), INDEX(invoice_id), INDEX(status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_wallet_holds (fresh installer safe).
CALL mj_safe_add_column('mj_wallet_holds', 'wallet_id', '`wallet_id` BIGINT NOT NULL');
CALL mj_safe_add_column('mj_wallet_holds', 'user_id', '`user_id` BIGINT NOT NULL');
CALL mj_safe_add_column('mj_wallet_holds', 'batch_id', '`batch_id` BIGINT NULL');
CALL mj_safe_add_column('mj_wallet_holds', 'invoice_id', '`invoice_id` BIGINT NULL');
CALL mj_safe_add_column('mj_wallet_holds', 'amount', '`amount` DECIMAL(12,2) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_wallet_holds', 'currency_code', '`currency_code` VARCHAR(10) NOT NULL DEFAULT ''SGD''');
CALL mj_safe_add_column('mj_wallet_holds', 'status', '`status` VARCHAR(40) NOT NULL DEFAULT ''held''');
CALL mj_safe_add_column('mj_wallet_holds', 'note', '`note` TEXT NULL');
CALL mj_safe_add_column('mj_wallet_holds', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');
CALL mj_safe_add_column('mj_wallet_holds', 'released_at', '`released_at` DATETIME NULL');
CALL mj_safe_add_column('mj_wallet_holds', 'captured_at', '`captured_at` DATETIME NULL');


-- v2.9.11 behavior notes:
-- 1) Wallet invoice payments are auto-approved only for normal invoices and cannot be used for wallet top-up invoices.
-- 2) Re-approving an already paid batch is blocked/idempotent to prevent duplicate wallet debit, receipt and service activation.
-- 3) Finance dashboard now separates batch paid amount, receipt amount, invoice paid amount, wallet payment debit and wallet top-up credit.



-- ============================================================
-- Source: install/migration_v2.9.12_admin_portal_ux_safe_reset_reports_clean_menu_invoice_print_automation.sql
-- ============================================================

-- MJ Marketplace v2.9.12 Admin Portal UX + Safe Reset + Reports + Clean Menu + Invoice Print + Automation
-- Run once after uploading v2.9.12 files.

INSERT INTO mj_settings(setting_key, setting_value) VALUES
('v292_admin_portal_ready','1'),
('auto_approve_listings','0'),
('auto_approve_companies','0'),
('auto_hide_reported_listing_count','3'),
('auto_listing_quality_check','1'),
('auto_duplicate_listing_check','1'),
('auto_create_renewal_invoice','1'),
('auto_renewal_invoice_days','7'),
('abandoned_invoice_followup_days','1,3,7'),
('abandoned_invoice_cancel_days','30'),
('manual_payment_approval_required','1'),
('wallet_topup_admin_approval_required','1'),
('high_amount_approval_threshold','1000.00')
ON DUPLICATE KEY UPDATE setting_value=VALUES(setting_value);

CREATE TABLE IF NOT EXISTS mj_admin_reset_logs (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  admin_id BIGINT NULL,
  admin_name VARCHAR(190) NULL,
  admin_email VARCHAR(190) NULL,
  reset_type VARCHAR(120) NOT NULL,
  selected_options TEXT NULL,
  affected_tables TEXT NULL,
  records_deleted BIGINT NOT NULL DEFAULT 0,
  files_deleted BIGINT NOT NULL DEFAULT 0,
  ip_address VARCHAR(80) NULL,
  user_agent VARCHAR(255) NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX(admin_id), INDEX(reset_type), INDEX(created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_admin_notes (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  admin_id BIGINT NULL,
  related_type VARCHAR(80) NOT NULL,
  related_id BIGINT NOT NULL DEFAULT 0,
  note TEXT NOT NULL,
  reminder_at DATETIME NULL,
  status VARCHAR(40) NOT NULL DEFAULT 'open',
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX(admin_id), INDEX(related_type,related_id), INDEX(status), INDEX(reminder_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_admin_notes (fresh installer safe).
CALL mj_safe_add_column('mj_admin_notes', 'admin_id', '`admin_id` BIGINT NULL');
CALL mj_safe_add_column('mj_admin_notes', 'related_type', '`related_type` VARCHAR(80) NOT NULL');
CALL mj_safe_add_column('mj_admin_notes', 'related_id', '`related_id` BIGINT NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_admin_notes', 'note', '`note` TEXT NOT NULL');
CALL mj_safe_add_column('mj_admin_notes', 'reminder_at', '`reminder_at` DATETIME NULL');
CALL mj_safe_add_column('mj_admin_notes', 'status', '`status` VARCHAR(40) NOT NULL DEFAULT ''open''');
CALL mj_safe_add_column('mj_admin_notes', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');


CREATE TABLE IF NOT EXISTS mj_support_tickets (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT NULL,
  subject VARCHAR(220) NOT NULL,
  ticket_type VARCHAR(80) NULL,
  priority VARCHAR(40) NOT NULL DEFAULT 'normal',
  status VARCHAR(40) NOT NULL DEFAULT 'open',
  message TEXT NULL,
  admin_id BIGINT NULL,
  admin_reply TEXT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  INDEX(user_id), INDEX(status), INDEX(ticket_type), INDEX(created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_support_tickets (fresh installer safe).
CALL mj_safe_add_column('mj_support_tickets', 'user_id', '`user_id` BIGINT NULL');
CALL mj_safe_add_column('mj_support_tickets', 'subject', '`subject` VARCHAR(220) NOT NULL');
CALL mj_safe_add_column('mj_support_tickets', 'ticket_type', '`ticket_type` VARCHAR(80) NULL');
CALL mj_safe_add_column('mj_support_tickets', 'priority', '`priority` VARCHAR(40) NOT NULL DEFAULT ''normal''');
CALL mj_safe_add_column('mj_support_tickets', 'status', '`status` VARCHAR(40) NOT NULL DEFAULT ''open''');
CALL mj_safe_add_column('mj_support_tickets', 'message', '`message` TEXT NULL');
CALL mj_safe_add_column('mj_support_tickets', 'admin_id', '`admin_id` BIGINT NULL');
CALL mj_safe_add_column('mj_support_tickets', 'admin_reply', '`admin_reply` TEXT NULL');
CALL mj_safe_add_column('mj_support_tickets', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');
CALL mj_safe_add_column('mj_support_tickets', 'updated_at', '`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');


CREATE TABLE IF NOT EXISTS mj_admin_exception_logs (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  exception_key VARCHAR(190) NOT NULL UNIQUE,
  exception_type VARCHAR(80) NOT NULL,
  severity VARCHAR(40) NOT NULL DEFAULT 'normal',
  title VARCHAR(220) NOT NULL,
  message TEXT NULL,
  action_url VARCHAR(255) NULL,
  related_type VARCHAR(80) NULL,
  related_id BIGINT NULL,
  status VARCHAR(40) NOT NULL DEFAULT 'open',
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  resolved_at DATETIME NULL,
  INDEX(exception_type), INDEX(severity), INDEX(status), INDEX(created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_lead_events (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT NULL,
  listing_id BIGINT NULL,
  company_id BIGINT NULL,
  event_type VARCHAR(80) NOT NULL,
  event_source VARCHAR(80) NULL,
  ip_address VARCHAR(80) NULL,
  user_agent VARCHAR(255) NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX(user_id), INDEX(listing_id), INDEX(company_id), INDEX(event_type), INDEX(created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_admin_role_permissions (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  role_key VARCHAR(80) NOT NULL,
  module_key VARCHAR(120) NOT NULL,
  can_view TINYINT(1) NOT NULL DEFAULT 1,
  can_create TINYINT(1) NOT NULL DEFAULT 0,
  can_edit TINYINT(1) NOT NULL DEFAULT 0,
  can_delete TINYINT(1) NOT NULL DEFAULT 0,
  can_approve TINYINT(1) NOT NULL DEFAULT 0,
  UNIQUE KEY uniq_role_module(role_key,module_key)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- Helpful indexes for fast report filters. Ignore duplicate-index warnings if your MySQL version reports them.
-- Add manually only if missing on older MySQL:
-- ALTER TABLE mj_listings ADD INDEX idx_status_created(status,created_at);
-- ALTER TABLE mj_companies ADD INDEX idx_status_created(status,created_at);
-- ALTER TABLE mj_payment_batches ADD INDEX idx_status_created(status,created_at);
-- ALTER TABLE mj_invoices ADD INDEX idx_status_created(status,created_at);
-- ALTER TABLE mj_receipts ADD INDEX idx_created(created_at);



-- ============================================================
-- Source: install/migration_v2.9.13_admin_countries_speed_hotfix.sql
-- ============================================================

-- MJ Marketplace v2.9.13 Admin Countries Speed Hotfix
-- Adds useful indexes for admin/countries.php search and sorting.
-- Backward compatible: adds iso3 column first if it is missing.
-- Safe for MySQL/MariaDB versions that do not support ADD INDEX.

DELIMITER $$

DROP PROCEDURE IF EXISTS mj_add_column_if_missing $$
CREATE PROCEDURE mj_add_column_if_missing(
  IN p_table_name VARCHAR(128),
  IN p_column_name VARCHAR(128),
  IN p_column_sql TEXT
)
BEGIN
  IF NOT EXISTS (
    SELECT 1
      FROM information_schema.columns
     WHERE table_schema = DATABASE()
       AND table_name = p_table_name
       AND column_name = p_column_name
     LIMIT 1
  ) THEN
    SET @mj_sql = p_column_sql;
    PREPARE stmt FROM @mj_sql;
    EXECUTE stmt;
    DEALLOCATE PREPARE stmt;
  END IF;
END $$

DROP PROCEDURE IF EXISTS mj_add_index_if_missing $$
CREATE PROCEDURE mj_add_index_if_missing(
  IN p_table_name VARCHAR(128),
  IN p_index_name VARCHAR(128),
  IN p_index_sql TEXT
)
BEGIN
  IF NOT EXISTS (
    SELECT 1
      FROM information_schema.statistics
     WHERE table_schema = DATABASE()
       AND table_name = p_table_name
       AND index_name = p_index_name
     LIMIT 1
  ) THEN
    SET @mj_sql = p_index_sql;
    PREPARE stmt FROM @mj_sql;
    EXECUTE stmt;
    DEALLOCATE PREPARE stmt;
  END IF;
END $$

CALL mj_add_column_if_missing('mj_countries', 'iso3', 'ALTER TABLE mj_countries ADD COLUMN iso3 VARCHAR(3) NULL AFTER iso2') $$

CALL mj_add_index_if_missing('mj_countries', 'idx_mj_countries_active_name', 'ALTER TABLE mj_countries ADD INDEX idx_mj_countries_active_name (is_active, name)') $$
CALL mj_add_index_if_missing('mj_countries', 'idx_mj_countries_iso2', 'ALTER TABLE mj_countries ADD INDEX idx_mj_countries_iso2 (iso2)') $$
CALL mj_add_index_if_missing('mj_countries', 'idx_mj_countries_iso3', 'ALTER TABLE mj_countries ADD INDEX idx_mj_countries_iso3 (iso3)') $$
CALL mj_add_index_if_missing('mj_countries', 'idx_mj_countries_slug', 'ALTER TABLE mj_countries ADD INDEX idx_mj_countries_slug (slug)') $$
CALL mj_add_index_if_missing('mj_countries', 'idx_mj_countries_currency', 'ALTER TABLE mj_countries ADD INDEX idx_mj_countries_currency (currency_code)') $$

DROP PROCEDURE IF EXISTS mj_add_index_if_missing $$
DROP PROCEDURE IF EXISTS mj_add_column_if_missing $$

DELIMITER ;



-- ============================================================
-- Source: install/migration_v2.9.14_admin_countries_iso3_compatibility_fix.sql
-- ============================================================

-- MJ Marketplace v2.9.14 Admin Countries ISO3 Compatibility Fix
-- Fixes MySQL #1072: Key column 'iso3' doesn't exist in table.
-- Safe to run even if v2.9.13 was already partially applied or failed.

DELIMITER $$

DROP PROCEDURE IF EXISTS mj_add_column_if_missing $$
CREATE PROCEDURE mj_add_column_if_missing(
  IN p_table_name VARCHAR(128),
  IN p_column_name VARCHAR(128),
  IN p_column_sql TEXT
)
BEGIN
  IF NOT EXISTS (
    SELECT 1
      FROM information_schema.columns
     WHERE table_schema = DATABASE()
       AND table_name = p_table_name
       AND column_name = p_column_name
     LIMIT 1
  ) THEN
    SET @mj_sql = p_column_sql;
    PREPARE stmt FROM @mj_sql;
    EXECUTE stmt;
    DEALLOCATE PREPARE stmt;
  END IF;
END $$

DROP PROCEDURE IF EXISTS mj_add_index_if_missing $$
CREATE PROCEDURE mj_add_index_if_missing(
  IN p_table_name VARCHAR(128),
  IN p_index_name VARCHAR(128),
  IN p_index_sql TEXT
)
BEGIN
  IF NOT EXISTS (
    SELECT 1
      FROM information_schema.statistics
     WHERE table_schema = DATABASE()
       AND table_name = p_table_name
       AND index_name = p_index_name
     LIMIT 1
  ) THEN
    SET @mj_sql = p_index_sql;
    PREPARE stmt FROM @mj_sql;
    EXECUTE stmt;
    DEALLOCATE PREPARE stmt;
  END IF;
END $$

CALL mj_add_column_if_missing('mj_countries', 'iso3', 'ALTER TABLE mj_countries ADD COLUMN iso3 VARCHAR(3) NULL AFTER iso2') $$
CALL mj_add_index_if_missing('mj_countries', 'idx_mj_countries_iso3', 'ALTER TABLE mj_countries ADD INDEX idx_mj_countries_iso3 (iso3)') $$

DROP PROCEDURE IF EXISTS mj_add_index_if_missing $$
DROP PROCEDURE IF EXISTS mj_add_column_if_missing $$

DELIMITER ;



-- ============================================================
-- Source: install/migration_v2.9.15_payment_scope_lock_customer_to_seller_disabled.sql
-- ============================================================

-- MJ Marketplace v2.9.15 Payment Scope Lock
-- Business rule: customer-to-seller/user payments are disabled across the website.
-- Only user-to-admin billing remains enabled through Billing & Payments / Payment Center.

INSERT INTO mj_settings(setting_key, setting_value) VALUES
('customer_to_user_payments_disabled','1'),
('marketplace_payment_scope_locked','1'),
('order_online_payment_enabled','0'),
('order_manual_payment_enabled','0'),
('order_payment_proof_enabled','0'),
('order_payment_instructions_visible','0'),
('order_paypal_enabled','0'),
('order_stripe_enabled','0'),
('order_pay_at_shop_enabled','0'),
('order_payment_verify_role','disabled'),
('order_bank_instructions',''),
('order_paynow_instructions','')
ON DUPLICATE KEY UPDATE setting_value=VALUES(setting_value), updated_at=NOW();

DELIMITER $$

DROP PROCEDURE IF EXISTS mj_exec_if_table_exists $$
CREATE PROCEDURE mj_exec_if_table_exists(IN p_table_name VARCHAR(128), IN p_sql TEXT)
BEGIN
  IF EXISTS (
    SELECT 1 FROM information_schema.tables
     WHERE table_schema = DATABASE()
       AND table_name = p_table_name
     LIMIT 1
  ) THEN
    SET @mj_sql = p_sql;
    PREPARE stmt FROM @mj_sql;
    EXECUTE stmt;
    DEALLOCATE PREPARE stmt;
  END IF;
END $$

CALL mj_exec_if_table_exists('mj_orders', 'UPDATE mj_orders SET payment_status=''payment_disabled'', payment_method=NULL, updated_at=NOW() WHERE payment_status IN (''pending'',''proof_uploaded'',''payment_verified'',''verified'',''rejected'',''refunded'') AND (source_type IN (''offer'',''quote'',''deal'',''service'') OR source_type IS NULL OR source_type='''')') $$

CALL mj_exec_if_table_exists('mj_order_payment_proofs', 'UPDATE mj_order_payment_proofs SET status=''disabled'', admin_note=CONCAT(COALESCE(admin_note,''''), CASE WHEN COALESCE(admin_note,'''')='''' THEN '''' ELSE ''\n'' END, ''Disabled by v2.9.15 payment scope lock: customer-to-seller payment is not processed by MJ Marketplace.'') WHERE status IN (''submitted'',''uploaded'',''pending'',''pending_review'',''proof_uploaded'',''payment_uploaded'',''need_more_info'',''rejected'',''verified'',''approved'',''paid'')') $$

DROP PROCEDURE IF EXISTS mj_exec_if_table_exists $$

DELIMITER ;



-- ============================================================
-- Source: install/migration_v2.9.16_complete_deals_display_enquiry_multi_smtp.sql
-- ============================================================

-- MJ Marketplace v2.9.16 Complete Deals Display + Enquiry Module + Multi SMTP Alerts
-- Business rule kept: customers cannot pay sellers/users through MJ Marketplace.
-- Deals are display, enquiry, WhatsApp/call/email/contact and lead tracking only.

INSERT INTO mj_settings(setting_key, setting_value) VALUES
('customer_to_user_payments_disabled','1'),
('marketplace_payment_scope_locked','1'),
('deal_customer_checkout_enabled','0'),
('deal_voucher_purchase_enabled','0'),
('deal_customer_payment_enabled','0'),
('deal_merchant_payout_enabled','0'),
('deal_refund_enabled','0'),
('deal_auto_approve','0'),
('email_queue_enabled','1'),
('smtp_delivery_mode','failover'),
('smtp_allow_mail_fallback','1'),
('smtp_timeout_seconds','15'),
('smtp_default_from_name','MJ Marketplace')
ON DUPLICATE KEY UPDATE setting_value=VALUES(setting_value), updated_at=NOW();

DELIMITER $$

DROP PROCEDURE IF EXISTS mj_add_column_if_missing $$
CREATE PROCEDURE mj_add_column_if_missing(IN p_table_name VARCHAR(128), IN p_column_name VARCHAR(128), IN p_sql TEXT)
BEGIN
  IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = p_table_name AND column_name = p_column_name LIMIT 1) THEN
    SET @mj_sql = p_sql;
    PREPARE stmt FROM @mj_sql;
    EXECUTE stmt;
    DEALLOCATE PREPARE stmt;
  END IF;
END $$

DROP PROCEDURE IF EXISTS mj_add_index_if_missing $$
CREATE PROCEDURE mj_add_index_if_missing(IN p_table_name VARCHAR(128), IN p_index_name VARCHAR(128), IN p_sql TEXT)
BEGIN
  IF NOT EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = p_table_name AND index_name = p_index_name LIMIT 1) THEN
    SET @mj_sql = p_sql;
    PREPARE stmt FROM @mj_sql;
    EXECUTE stmt;
    DEALLOCATE PREPARE stmt;
  END IF;
END $$

CREATE TABLE IF NOT EXISTS mj_deals (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT NULL,
  company_id BIGINT NULL,
  category_id INT NULL,
  country_id INT NULL,
  city_id BIGINT NULL,
  deal_type VARCHAR(60) NOT NULL DEFAULT 'normal',
  title VARCHAR(255) NOT NULL,
  slug VARCHAR(255) NULL,
  short_description VARCHAR(500) NULL,
  description MEDIUMTEXT NULL,
  image_path VARCHAR(500) NULL,
  original_price DECIMAL(12,2) DEFAULT 0,
  deal_price DECIMAL(12,2) DEFAULT 0,
  discount_percent DECIMAL(8,2) DEFAULT 0,
  price_note VARCHAR(255) NULL,
  currency_code VARCHAR(8) DEFAULT 'SGD',
  start_at DATETIME NULL,
  end_at DATETIME NULL,
  status VARCHAR(40) NOT NULL DEFAULT 'pending',
  is_featured TINYINT(1) NOT NULL DEFAULT 0,
  is_sponsored TINYINT(1) NOT NULL DEFAULT 0,
  featured_until DATETIME NULL,
  sponsored_until DATETIME NULL,
  contact_phone VARCHAR(80) NULL,
  contact_whatsapp VARCHAR(80) NULL,
  contact_email VARCHAR(190) NULL,
  terms TEXT NULL,
  quality_score INT NOT NULL DEFAULT 0,
  view_count INT NOT NULL DEFAULT 0,
  enquiry_count INT NOT NULL DEFAULT 0,
  whatsapp_click_count INT NOT NULL DEFAULT 0,
  call_click_count INT NOT NULL DEFAULT 0,
  email_click_count INT NOT NULL DEFAULT 0,
  share_count INT NOT NULL DEFAULT 0,
  qr_scan_count INT NOT NULL DEFAULT 0,
  reject_reason TEXT NULL,
  admin_note TEXT NULL,
  approved_by INT NULL,
  approved_at DATETIME NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 $$

CALL mj_add_column_if_missing('mj_deals','user_id','ALTER TABLE mj_deals ADD COLUMN user_id BIGINT NULL') $$
CALL mj_add_column_if_missing('mj_deals','deal_type','ALTER TABLE mj_deals ADD COLUMN deal_type VARCHAR(60) NOT NULL DEFAULT ''normal''') $$
CALL mj_add_column_if_missing('mj_deals','short_description','ALTER TABLE mj_deals ADD COLUMN short_description VARCHAR(500) NULL') $$
CALL mj_add_column_if_missing('mj_deals','price_note','ALTER TABLE mj_deals ADD COLUMN price_note VARCHAR(255) NULL') $$
CALL mj_add_column_if_missing('mj_deals','featured_until','ALTER TABLE mj_deals ADD COLUMN featured_until DATETIME NULL') $$
CALL mj_add_column_if_missing('mj_deals','contact_phone','ALTER TABLE mj_deals ADD COLUMN contact_phone VARCHAR(80) NULL') $$
CALL mj_add_column_if_missing('mj_deals','contact_whatsapp','ALTER TABLE mj_deals ADD COLUMN contact_whatsapp VARCHAR(80) NULL') $$
CALL mj_add_column_if_missing('mj_deals','contact_email','ALTER TABLE mj_deals ADD COLUMN contact_email VARCHAR(190) NULL') $$
CALL mj_add_column_if_missing('mj_deals','quality_score','ALTER TABLE mj_deals ADD COLUMN quality_score INT NOT NULL DEFAULT 0') $$
CALL mj_add_column_if_missing('mj_deals','view_count','ALTER TABLE mj_deals ADD COLUMN view_count INT NOT NULL DEFAULT 0') $$
CALL mj_add_column_if_missing('mj_deals','enquiry_count','ALTER TABLE mj_deals ADD COLUMN enquiry_count INT NOT NULL DEFAULT 0') $$
CALL mj_add_column_if_missing('mj_deals','whatsapp_click_count','ALTER TABLE mj_deals ADD COLUMN whatsapp_click_count INT NOT NULL DEFAULT 0') $$
CALL mj_add_column_if_missing('mj_deals','call_click_count','ALTER TABLE mj_deals ADD COLUMN call_click_count INT NOT NULL DEFAULT 0') $$
CALL mj_add_column_if_missing('mj_deals','email_click_count','ALTER TABLE mj_deals ADD COLUMN email_click_count INT NOT NULL DEFAULT 0') $$
CALL mj_add_column_if_missing('mj_deals','share_count','ALTER TABLE mj_deals ADD COLUMN share_count INT NOT NULL DEFAULT 0') $$
CALL mj_add_column_if_missing('mj_deals','qr_scan_count','ALTER TABLE mj_deals ADD COLUMN qr_scan_count INT NOT NULL DEFAULT 0') $$
CALL mj_add_column_if_missing('mj_deals','reject_reason','ALTER TABLE mj_deals ADD COLUMN reject_reason TEXT NULL') $$
CALL mj_add_column_if_missing('mj_deals','admin_note','ALTER TABLE mj_deals ADD COLUMN admin_note TEXT NULL') $$
CALL mj_add_column_if_missing('mj_deals','approved_by','ALTER TABLE mj_deals ADD COLUMN approved_by INT NULL') $$
CALL mj_add_column_if_missing('mj_deals','approved_at','ALTER TABLE mj_deals ADD COLUMN approved_at DATETIME NULL') $$

ALTER TABLE mj_deals MODIFY status VARCHAR(40) NOT NULL DEFAULT 'pending' $$
CALL mj_add_index_if_missing('mj_deals','idx_mj_deals_user_status','ALTER TABLE mj_deals ADD INDEX idx_mj_deals_user_status (user_id,status)') $$
CALL mj_add_index_if_missing('mj_deals','idx_mj_deals_country_status','ALTER TABLE mj_deals ADD INDEX idx_mj_deals_country_status (country_id,status)') $$
CALL mj_add_index_if_missing('mj_deals','idx_mj_deals_category_status','ALTER TABLE mj_deals ADD INDEX idx_mj_deals_category_status (category_id,status)') $$
CALL mj_add_index_if_missing('mj_deals','idx_mj_deals_end_at','ALTER TABLE mj_deals ADD INDEX idx_mj_deals_end_at (end_at)') $$

CREATE TABLE IF NOT EXISTS mj_deal_enquiries (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  deal_id BIGINT NOT NULL,
  seller_user_id BIGINT NULL,
  company_id BIGINT NULL,
  customer_user_id BIGINT NULL,
  name VARCHAR(160) NOT NULL,
  email VARCHAR(190) NULL,
  phone VARCHAR(80) NULL,
  preferred_datetime VARCHAR(120) NULL,
  message TEXT NULL,
  status VARCHAR(40) NOT NULL DEFAULT 'new',
  source VARCHAR(60) NOT NULL DEFAULT 'deal_page',
  admin_note TEXT NULL,
  ip_address VARCHAR(80) NULL,
  user_agent TEXT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NULL,
  INDEX idx_deal_status(deal_id,status),
  INDEX idx_seller_status(seller_user_id,status),
  INDEX idx_company(company_id),
  INDEX idx_created(created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 $$

CREATE TABLE IF NOT EXISTS mj_deal_events (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  deal_id BIGINT NOT NULL,
  user_id BIGINT NULL,
  event_type VARCHAR(80) NOT NULL,
  event_value VARCHAR(255) NULL,
  ip_address VARCHAR(80) NULL,
  user_agent TEXT NULL,
  session_key VARCHAR(191) NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_deal_type(deal_id,event_type),
  INDEX idx_created(created_at),
  INDEX idx_session(session_key)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 $$

CREATE TABLE IF NOT EXISTS mj_smtp_accounts (
  id INT AUTO_INCREMENT PRIMARY KEY,
  account_name VARCHAR(160) NOT NULL,
  host VARCHAR(190) NOT NULL,
  port INT NOT NULL DEFAULT 587,
  encryption VARCHAR(20) NOT NULL DEFAULT 'tls',
  username VARCHAR(190) NULL,
  password TEXT NULL,
  from_email VARCHAR(190) NOT NULL,
  from_name VARCHAR(190) NULL,
  reply_to_email VARCHAR(190) NULL,
  is_active TINYINT(1) NOT NULL DEFAULT 1,
  priority_order INT NOT NULL DEFAULT 100,
  daily_limit INT NOT NULL DEFAULT 0,
  sent_today INT NOT NULL DEFAULT 0,
  last_sent_at DATETIME NULL,
  last_error TEXT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  INDEX idx_active_priority(is_active, priority_order),
  INDEX idx_from_email(from_email)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 $$

CREATE TABLE IF NOT EXISTS mj_email_send_log (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  smtp_account_id INT NULL,
  to_email VARCHAR(190) NOT NULL,
  subject VARCHAR(255) NOT NULL,
  template_key VARCHAR(120) NULL,
  status VARCHAR(40) NOT NULL DEFAULT 'pending',
  error_message TEXT NULL,
  attempts INT NOT NULL DEFAULT 0,
  sent_at DATETIME NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_status_created(status, created_at),
  INDEX idx_to_email(to_email),
  INDEX idx_smtp_account(smtp_account_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 $$

CREATE TABLE IF NOT EXISTS mj_email_queue (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT NULL,
  admin_id INT NULL,
  to_email VARCHAR(191) NOT NULL,
  to_name VARCHAR(191) NULL,
  subject VARCHAR(255) NOT NULL,
  body_html LONGTEXT NULL,
  body_text LONGTEXT NULL,
  template_key VARCHAR(100) NULL,
  status VARCHAR(40) NOT NULL DEFAULT 'pending',
  priority VARCHAR(40) NOT NULL DEFAULT 'normal',
  attempts INT NOT NULL DEFAULT 0,
  scheduled_at DATETIME NULL,
  sent_at DATETIME NULL,
  failed_at DATETIME NULL,
  error_message TEXT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX(user_id), INDEX(status), INDEX(template_key), INDEX(scheduled_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 $$

CALL mj_add_column_if_missing('mj_email_queue','admin_id','ALTER TABLE mj_email_queue ADD COLUMN admin_id INT NULL') $$
CALL mj_add_column_if_missing('mj_email_queue','to_name','ALTER TABLE mj_email_queue ADD COLUMN to_name VARCHAR(191) NULL') $$
CALL mj_add_column_if_missing('mj_email_queue','attempts','ALTER TABLE mj_email_queue ADD COLUMN attempts INT NOT NULL DEFAULT 0') $$

DROP PROCEDURE IF EXISTS mj_add_index_if_missing $$
DROP PROCEDURE IF EXISTS mj_add_column_if_missing $$
DELIMITER ;
-- Auto repair columns for duplicate CREATE TABLE mj_smtp_accounts (fresh installer safe).
CALL mj_safe_add_column('mj_smtp_accounts', 'account_name', '`account_name` VARCHAR(160) NOT NULL');
CALL mj_safe_add_column('mj_smtp_accounts', 'host', '`host` VARCHAR(190) NOT NULL');
CALL mj_safe_add_column('mj_smtp_accounts', 'port', '`port` INT NOT NULL DEFAULT 587');
CALL mj_safe_add_column('mj_smtp_accounts', 'encryption', '`encryption` VARCHAR(20) NOT NULL DEFAULT ''tls''');
CALL mj_safe_add_column('mj_smtp_accounts', 'username', '`username` VARCHAR(190) NULL');
CALL mj_safe_add_column('mj_smtp_accounts', 'password', '`password` TEXT NULL');
CALL mj_safe_add_column('mj_smtp_accounts', 'from_email', '`from_email` VARCHAR(190) NOT NULL');
CALL mj_safe_add_column('mj_smtp_accounts', 'from_name', '`from_name` VARCHAR(190) NULL');
CALL mj_safe_add_column('mj_smtp_accounts', 'reply_to_email', '`reply_to_email` VARCHAR(190) NULL');
CALL mj_safe_add_column('mj_smtp_accounts', 'is_active', '`is_active` TINYINT(1) NOT NULL DEFAULT 1');
CALL mj_safe_add_column('mj_smtp_accounts', 'priority_order', '`priority_order` INT NOT NULL DEFAULT 100');
CALL mj_safe_add_column('mj_smtp_accounts', 'daily_limit', '`daily_limit` INT NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_smtp_accounts', 'sent_today', '`sent_today` INT NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_smtp_accounts', 'last_sent_at', '`last_sent_at` DATETIME NULL');
CALL mj_safe_add_column('mj_smtp_accounts', 'last_error', '`last_error` TEXT NULL');
CALL mj_safe_add_column('mj_smtp_accounts', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');
CALL mj_safe_add_column('mj_smtp_accounts', 'updated_at', '`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');




-- ============================================================
-- Source: install/migration_v2.9.17_admin_ease_update_review_workflow.sql
-- ============================================================

-- MJ Marketplace v2.9.17 Admin Ease + Listing/Company/Deal Update Review Workflow
-- Active public records remain live until admin approves user-submitted edits.

INSERT INTO mj_settings(setting_key, setting_value) VALUES
('listing_update_review_required','1'),
('company_update_review_required','1'),
('deal_update_review_required','1'),
('admin_update_review_center_enabled','1')
ON DUPLICATE KEY UPDATE setting_value=VALUES(setting_value), updated_at=NOW();

DELIMITER $$

DROP PROCEDURE IF EXISTS mj_add_column_if_missing $$
CREATE PROCEDURE mj_add_column_if_missing(IN p_table_name VARCHAR(128), IN p_column_name VARCHAR(128), IN p_sql TEXT)
BEGIN
  IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = p_table_name AND column_name = p_column_name LIMIT 1) THEN
    SET @mj_sql = p_sql;
    PREPARE stmt FROM @mj_sql;
    EXECUTE stmt;
    DEALLOCATE PREPARE stmt;
  END IF;
END $$

DROP PROCEDURE IF EXISTS mj_add_index_if_missing $$
CREATE PROCEDURE mj_add_index_if_missing(IN p_table_name VARCHAR(128), IN p_index_name VARCHAR(128), IN p_sql TEXT)
BEGIN
  IF NOT EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = p_table_name AND index_name = p_index_name LIMIT 1) THEN
    SET @mj_sql = p_sql;
    PREPARE stmt FROM @mj_sql;
    EXECUTE stmt;
    DEALLOCATE PREPARE stmt;
  END IF;
END $$

DELIMITER ;

CREATE TABLE IF NOT EXISTS mj_company_revisions (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  company_id BIGINT NOT NULL,
  user_id BIGINT NULL,
  old_data_json LONGTEXT NULL,
  new_data_json LONGTEXT NULL,
  old_extra_json LONGTEXT NULL,
  new_extra_json LONGTEXT NULL,
  status VARCHAR(40) NOT NULL DEFAULT 'pending',
  admin_id BIGINT NULL,
  admin_note TEXT NULL,
  reject_reason TEXT NULL,
  submitted_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  reviewed_at DATETIME NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_company_status(company_id,status),
  INDEX idx_user_status(user_id,status),
  INDEX idx_submitted(submitted_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_company_revisions (fresh installer safe).
CALL mj_safe_add_column('mj_company_revisions', 'company_id', '`company_id` BIGINT NOT NULL');
CALL mj_safe_add_column('mj_company_revisions', 'user_id', '`user_id` BIGINT NULL');
CALL mj_safe_add_column('mj_company_revisions', 'old_data_json', '`old_data_json` LONGTEXT NULL');
CALL mj_safe_add_column('mj_company_revisions', 'new_data_json', '`new_data_json` LONGTEXT NULL');
CALL mj_safe_add_column('mj_company_revisions', 'old_extra_json', '`old_extra_json` LONGTEXT NULL');
CALL mj_safe_add_column('mj_company_revisions', 'new_extra_json', '`new_extra_json` LONGTEXT NULL');
CALL mj_safe_add_column('mj_company_revisions', 'status', '`status` VARCHAR(40) NOT NULL DEFAULT ''pending''');
CALL mj_safe_add_column('mj_company_revisions', 'admin_id', '`admin_id` BIGINT NULL');
CALL mj_safe_add_column('mj_company_revisions', 'admin_note', '`admin_note` TEXT NULL');
CALL mj_safe_add_column('mj_company_revisions', 'reject_reason', '`reject_reason` TEXT NULL');
CALL mj_safe_add_column('mj_company_revisions', 'submitted_at', '`submitted_at` DATETIME DEFAULT CURRENT_TIMESTAMP');
CALL mj_safe_add_column('mj_company_revisions', 'reviewed_at', '`reviewed_at` DATETIME NULL');
CALL mj_safe_add_column('mj_company_revisions', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');


CREATE TABLE IF NOT EXISTS mj_deal_revisions (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  deal_id BIGINT NOT NULL,
  user_id BIGINT NULL,
  old_data_json LONGTEXT NULL,
  new_data_json LONGTEXT NULL,
  old_extra_json LONGTEXT NULL,
  new_extra_json LONGTEXT NULL,
  status VARCHAR(40) NOT NULL DEFAULT 'pending',
  admin_id BIGINT NULL,
  admin_note TEXT NULL,
  reject_reason TEXT NULL,
  submitted_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  reviewed_at DATETIME NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_deal_status(deal_id,status),
  INDEX idx_user_status(user_id,status),
  INDEX idx_submitted(submitted_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_deal_revisions (fresh installer safe).
CALL mj_safe_add_column('mj_deal_revisions', 'deal_id', '`deal_id` BIGINT NOT NULL');
CALL mj_safe_add_column('mj_deal_revisions', 'user_id', '`user_id` BIGINT NULL');
CALL mj_safe_add_column('mj_deal_revisions', 'old_data_json', '`old_data_json` LONGTEXT NULL');
CALL mj_safe_add_column('mj_deal_revisions', 'new_data_json', '`new_data_json` LONGTEXT NULL');
CALL mj_safe_add_column('mj_deal_revisions', 'old_extra_json', '`old_extra_json` LONGTEXT NULL');
CALL mj_safe_add_column('mj_deal_revisions', 'new_extra_json', '`new_extra_json` LONGTEXT NULL');
CALL mj_safe_add_column('mj_deal_revisions', 'status', '`status` VARCHAR(40) NOT NULL DEFAULT ''pending''');
CALL mj_safe_add_column('mj_deal_revisions', 'admin_id', '`admin_id` BIGINT NULL');
CALL mj_safe_add_column('mj_deal_revisions', 'admin_note', '`admin_note` TEXT NULL');
CALL mj_safe_add_column('mj_deal_revisions', 'reject_reason', '`reject_reason` TEXT NULL');
CALL mj_safe_add_column('mj_deal_revisions', 'submitted_at', '`submitted_at` DATETIME DEFAULT CURRENT_TIMESTAMP');
CALL mj_safe_add_column('mj_deal_revisions', 'reviewed_at', '`reviewed_at` DATETIME NULL');
CALL mj_safe_add_column('mj_deal_revisions', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');


CALL mj_add_column_if_missing('mj_listing_revisions', 'old_images_json', 'ALTER TABLE mj_listing_revisions ADD COLUMN old_images_json LONGTEXT NULL AFTER new_data_json');
CALL mj_add_column_if_missing('mj_listing_revisions', 'new_images_json', 'ALTER TABLE mj_listing_revisions ADD COLUMN new_images_json LONGTEXT NULL AFTER old_images_json');
CALL mj_add_column_if_missing('mj_listing_revisions', 'deleted_images_json', 'ALTER TABLE mj_listing_revisions ADD COLUMN deleted_images_json LONGTEXT NULL AFTER new_images_json');
CALL mj_add_index_if_missing('mj_listing_revisions', 'idx_status_submitted', 'ALTER TABLE mj_listing_revisions ADD INDEX idx_status_submitted (status, submitted_at)');

-- Keep procedures for older MySQL sessions harmless by removing them after migration.
DROP PROCEDURE IF EXISTS mj_add_column_if_missing;
DROP PROCEDURE IF EXISTS mj_add_index_if_missing;



-- ============================================================
-- Source: install/migration_v2.9.18_admin_deal_import_option.sql
-- ============================================================

-- MJ Marketplace v2.9.18 Admin Deal Import Option
-- Adds sample CSV deal import with preview/validation/logging.
-- Deals remain display/enquiry only. No customer-to-seller payment flow is created.

INSERT INTO mj_settings(setting_key, setting_value) VALUES
('deal_import_enabled','1'),
('deal_import_default_status','draft'),
('deal_import_customer_payment_enabled','0')
ON DUPLICATE KEY UPDATE setting_value=VALUES(setting_value), updated_at=NOW();

CREATE TABLE IF NOT EXISTS mj_deal_import_logs (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  admin_id INT NULL,
  filename VARCHAR(255) NULL,
  total_rows INT NOT NULL DEFAULT 0,
  imported_rows INT NOT NULL DEFAULT 0,
  updated_rows INT NOT NULL DEFAULT 0,
  skipped_rows INT NOT NULL DEFAULT 0,
  failed_rows INT NOT NULL DEFAULT 0,
  default_status VARCHAR(40) NULL,
  duplicate_mode VARCHAR(40) NULL,
  error_json LONGTEXT NULL,
  ip_address VARCHAR(80) NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_created(created_at),
  INDEX idx_admin_created(admin_id, created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_deal_import_logs (fresh installer safe).
CALL mj_safe_add_column('mj_deal_import_logs', 'admin_id', '`admin_id` INT NULL');
CALL mj_safe_add_column('mj_deal_import_logs', 'filename', '`filename` VARCHAR(255) NULL');
CALL mj_safe_add_column('mj_deal_import_logs', 'total_rows', '`total_rows` INT NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_deal_import_logs', 'imported_rows', '`imported_rows` INT NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_deal_import_logs', 'updated_rows', '`updated_rows` INT NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_deal_import_logs', 'skipped_rows', '`skipped_rows` INT NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_deal_import_logs', 'failed_rows', '`failed_rows` INT NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_deal_import_logs', 'default_status', '`default_status` VARCHAR(40) NULL');
CALL mj_safe_add_column('mj_deal_import_logs', 'duplicate_mode', '`duplicate_mode` VARCHAR(40) NULL');
CALL mj_safe_add_column('mj_deal_import_logs', 'error_json', '`error_json` LONGTEXT NULL');
CALL mj_safe_add_column('mj_deal_import_logs', 'ip_address', '`ip_address` VARCHAR(80) NULL');
CALL mj_safe_add_column('mj_deal_import_logs', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');


-- Keep deal payment scope locked.
UPDATE mj_settings SET setting_value='0', updated_at=NOW() WHERE setting_key IN ('deal_customer_payment_enabled','deal_merchant_payout_enabled','deal_refund_enabled','deal_import_customer_payment_enabled');



-- ============================================================
-- Source: install/migration_v2.9.19_admin_dashboard_cart.sql
-- ============================================================

-- MJ Marketplace v2.9.19 Admin Dashboard Cleanup + User-to-Admin Cart
-- Cart scope: only user/seller pays admin. Customer-to-seller payment remains disabled.

INSERT INTO mj_settings(setting_key, setting_value) VALUES
('admin_service_cart_enabled','1'),
('deal_featured_charge_amount','10.00'),
('deal_sponsored_charge_amount','15.00'),
('deal_promotion_default_days','7'),
('deal_promotion_max_days','90'),
('cart_abandoned_reminder_days','1,3,7'),
('customer_to_user_payments_disabled','1'),
('marketplace_payment_scope_locked','1')
ON DUPLICATE KEY UPDATE setting_value=VALUES(setting_value), updated_at=NOW();

CREATE TABLE IF NOT EXISTS mj_admin_charge_cart (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  item_type VARCHAR(80) NOT NULL,
  target_type VARCHAR(80) NULL,
  target_id BIGINT NULL,
  title VARCHAR(190) NOT NULL,
  description TEXT NULL,
  quantity INT NOT NULL DEFAULT 1,
  unit_amount DECIMAL(12,2) NOT NULL DEFAULT 0,
  total_amount DECIMAL(12,2) NOT NULL DEFAULT 0,
  currency_code VARCHAR(10) NOT NULL DEFAULT 'SGD',
  duration_days INT NULL,
  status VARCHAR(40) NOT NULL DEFAULT 'cart',
  metadata_json MEDIUMTEXT NULL,
  batch_id BIGINT NULL,
  invoice_id BIGINT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  INDEX idx_user_status(user_id,status),
  INDEX idx_type(item_type),
  INDEX idx_target(target_type,target_id),
  INDEX idx_batch(batch_id),
  INDEX idx_created(created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_admin_charge_cart (fresh installer safe).
CALL mj_safe_add_column('mj_admin_charge_cart', 'user_id', '`user_id` BIGINT NOT NULL');
CALL mj_safe_add_column('mj_admin_charge_cart', 'item_type', '`item_type` VARCHAR(80) NOT NULL');
CALL mj_safe_add_column('mj_admin_charge_cart', 'target_type', '`target_type` VARCHAR(80) NULL');
CALL mj_safe_add_column('mj_admin_charge_cart', 'target_id', '`target_id` BIGINT NULL');
CALL mj_safe_add_column('mj_admin_charge_cart', 'title', '`title` VARCHAR(190) NOT NULL');
CALL mj_safe_add_column('mj_admin_charge_cart', 'description', '`description` TEXT NULL');
CALL mj_safe_add_column('mj_admin_charge_cart', 'quantity', '`quantity` INT NOT NULL DEFAULT 1');
CALL mj_safe_add_column('mj_admin_charge_cart', 'unit_amount', '`unit_amount` DECIMAL(12,2) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_admin_charge_cart', 'total_amount', '`total_amount` DECIMAL(12,2) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_admin_charge_cart', 'currency_code', '`currency_code` VARCHAR(10) NOT NULL DEFAULT ''SGD''');
CALL mj_safe_add_column('mj_admin_charge_cart', 'duration_days', '`duration_days` INT NULL');
CALL mj_safe_add_column('mj_admin_charge_cart', 'status', '`status` VARCHAR(40) NOT NULL DEFAULT ''cart''');
CALL mj_safe_add_column('mj_admin_charge_cart', 'metadata_json', '`metadata_json` MEDIUMTEXT NULL');
CALL mj_safe_add_column('mj_admin_charge_cart', 'batch_id', '`batch_id` BIGINT NULL');
CALL mj_safe_add_column('mj_admin_charge_cart', 'invoice_id', '`invoice_id` BIGINT NULL');
CALL mj_safe_add_column('mj_admin_charge_cart', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');
CALL mj_safe_add_column('mj_admin_charge_cart', 'updated_at', '`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');


DELIMITER $$
DROP PROCEDURE IF EXISTS mj_v2919_add_column_if_missing $$
CREATE PROCEDURE mj_v2919_add_column_if_missing(IN p_table_name VARCHAR(128), IN p_column_name VARCHAR(128), IN p_sql TEXT)
BEGIN
  IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = p_table_name AND column_name = p_column_name LIMIT 1) THEN
    SET @mj_sql = p_sql;
    PREPARE stmt FROM @mj_sql;
    EXECUTE stmt;
    DEALLOCATE PREPARE stmt;
  END IF;
END $$

CALL mj_v2919_add_column_if_missing('mj_admin_charge_cart','duration_days','ALTER TABLE mj_admin_charge_cart ADD COLUMN duration_days INT NULL AFTER currency_code') $$
CALL mj_v2919_add_column_if_missing('mj_admin_charge_cart','batch_id','ALTER TABLE mj_admin_charge_cart ADD COLUMN batch_id BIGINT NULL AFTER metadata_json') $$
CALL mj_v2919_add_column_if_missing('mj_admin_charge_cart','invoice_id','ALTER TABLE mj_admin_charge_cart ADD COLUMN invoice_id BIGINT NULL AFTER batch_id') $$

DROP PROCEDURE IF EXISTS mj_v2919_add_column_if_missing $$
DELIMITER ;

-- Keep disabled pages/payment scope locked.
UPDATE mj_settings SET setting_value='0', updated_at=NOW() WHERE setting_key IN ('deal_customer_checkout_enabled','deal_voucher_purchase_enabled','deal_customer_payment_enabled','deal_merchant_payout_enabled','deal_refund_enabled');



-- ============================================================
-- Source: install/migration_v3.0.0_full_marketplace_upgrade.sql
-- ============================================================

-- MJ Marketplace v3.0.0 Full Admin + User + Frontend Automation Upgrade
-- Scope lock: customers cannot pay sellers/users through MJ Marketplace.

INSERT INTO mj_settings(setting_key, setting_value) VALUES
('mj_v3_enabled','1'),('customer_to_user_payments_disabled','1'),('marketplace_payment_scope_locked','1'),('global_notifications_enabled','1'),('global_notification_poll_seconds','25'),('browser_notifications_enabled','1'),('notification_popup_position','bottom-right'),('notification_sound_enabled','0'),('default_max_deals_per_user','3'),('extra_deal_slot_charge_amount','5.00'),('deal_renewal_charge_amount','5.00'),('deal_renewal_default_days','30'),('deal_homepage_charge_amount','25.00'),('deal_bump_charge_amount','3.00'),('deal_urgent_charge_amount','3.00'),('deal_upgrade_default_days','7'),('deal_upgrade_max_days','90'),('seller_dashboard_charts_enabled','1'),('admin_dashboard_charts_enabled','1')
ON DUPLICATE KEY UPDATE setting_value=VALUES(setting_value), updated_at=NOW();

CREATE TABLE IF NOT EXISTS mj_browser_push_subscriptions (id BIGINT AUTO_INCREMENT PRIMARY KEY,user_id BIGINT NULL,admin_id BIGINT NULL,customer_token VARCHAR(191) NULL,endpoint TEXT NULL,auth_key VARCHAR(255) NULL,p256dh_key VARCHAR(255) NULL,permission_status VARCHAR(40) NULL,user_agent TEXT NULL,last_seen_at DATETIME NULL,created_at DATETIME DEFAULT CURRENT_TIMESTAMP,INDEX(user_id),INDEX(admin_id),INDEX(customer_token)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

DELIMITER $$
DROP PROCEDURE IF EXISTS mj_v300_add_column_if_missing $$
CREATE PROCEDURE mj_v300_add_column_if_missing(IN p_table VARCHAR(128), IN p_col VARCHAR(128), IN p_sql TEXT)
BEGIN
  IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name=p_table LIMIT 1)
  AND NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name=p_table AND column_name=p_col LIMIT 1) THEN
    SET @s=p_sql; PREPARE stmt FROM @s; EXECUTE stmt; DEALLOCATE PREPARE stmt;
  END IF;
END $$
CALL mj_v300_add_column_if_missing('mj_user_limits','max_deals','ALTER TABLE mj_user_limits ADD COLUMN max_deals INT NULL AFTER max_companies') $$
CALL mj_v300_add_column_if_missing('mj_user_limits','deal_auto_approve','ALTER TABLE mj_user_limits ADD COLUMN deal_auto_approve TINYINT(1) NULL AFTER max_deals') $$
CALL mj_v300_add_column_if_missing('mj_user_limits','allow_sponsored_deal','ALTER TABLE mj_user_limits ADD COLUMN allow_sponsored_deal TINYINT(1) NULL AFTER deal_auto_approve') $$
CALL mj_v300_add_column_if_missing('mj_user_limits','allow_featured_deal','ALTER TABLE mj_user_limits ADD COLUMN allow_featured_deal TINYINT(1) NULL AFTER allow_sponsored_deal') $$
CALL mj_v300_add_column_if_missing('mj_deals','sponsored_until','ALTER TABLE mj_deals ADD COLUMN sponsored_until DATETIME NULL AFTER is_sponsored') $$
CALL mj_v300_add_column_if_missing('mj_deals','is_urgent','ALTER TABLE mj_deals ADD COLUMN is_urgent TINYINT(1) NOT NULL DEFAULT 0 AFTER sponsored_until') $$
CALL mj_v300_add_column_if_missing('mj_deals','urgent_until','ALTER TABLE mj_deals ADD COLUMN urgent_until DATETIME NULL AFTER is_urgent') $$
CALL mj_v300_add_column_if_missing('mj_deals','is_bumped','ALTER TABLE mj_deals ADD COLUMN is_bumped TINYINT(1) NOT NULL DEFAULT 0 AFTER urgent_until') $$
CALL mj_v300_add_column_if_missing('mj_deals','bumped_until','ALTER TABLE mj_deals ADD COLUMN bumped_until DATETIME NULL AFTER is_bumped') $$
CALL mj_v300_add_column_if_missing('mj_deals','is_homepage','ALTER TABLE mj_deals ADD COLUMN is_homepage TINYINT(1) NOT NULL DEFAULT 0 AFTER bumped_until') $$
CALL mj_v300_add_column_if_missing('mj_deals','homepage_until','ALTER TABLE mj_deals ADD COLUMN homepage_until DATETIME NULL AFTER is_homepage') $$
CALL mj_v300_add_column_if_missing('mj_deals','renewed_until','ALTER TABLE mj_deals ADD COLUMN renewed_until DATETIME NULL AFTER homepage_until') $$
DROP PROCEDURE IF EXISTS mj_v300_add_column_if_missing $$
DELIMITER ;
UPDATE mj_settings SET setting_value='0', updated_at=NOW() WHERE setting_key IN ('deal_customer_checkout_enabled','deal_voucher_purchase_enabled','deal_customer_payment_enabled','deal_merchant_payout_enabled','deal_refund_enabled','customer_checkout_enabled');



-- ============================================================
-- Source: install/migration_v3.0.1_billing_status_display_hotfix.sql
-- ============================================================

-- MJ Marketplace v3.0.1 - Billing status display hotfix
-- Safe migration: normalizes blank receipt status values and marks migration applied.

UPDATE mj_receipts SET status='paid' WHERE status IS NULL OR TRIM(status)='';

INSERT INTO mj_settings(setting_key, setting_value) VALUES('v301_billing_status_display_hotfix','1')
ON DUPLICATE KEY UPDATE setting_value=VALUES(setting_value);



-- ============================================================
-- Source: install/migration_v3.0.2_user_leads_inbox_global_enquiry_notifications.sql
-- ============================================================

-- MJ Marketplace v3.0.2 - User Leads Inbox + Global Enquiry Notifications
-- Keeps customer-to-seller payment disabled. This migration only improves CRM/leads/notification follow-up.

CREATE TABLE IF NOT EXISTS mj_lead_notes (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  customer_id BIGINT NOT NULL,
  actor_user_id BIGINT NULL,
  actor_admin_id BIGINT NULL,
  note TEXT NULL,
  status_from VARCHAR(40) NULL,
  status_to VARCHAR(40) NULL,
  followup_at DATETIME NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX(customer_id), INDEX(actor_user_id), INDEX(actor_admin_id), INDEX(created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_lead_notes (fresh installer safe).
CALL mj_safe_add_column('mj_lead_notes', 'customer_id', '`customer_id` BIGINT NOT NULL');
CALL mj_safe_add_column('mj_lead_notes', 'actor_user_id', '`actor_user_id` BIGINT NULL');
CALL mj_safe_add_column('mj_lead_notes', 'actor_admin_id', '`actor_admin_id` BIGINT NULL');
CALL mj_safe_add_column('mj_lead_notes', 'note', '`note` TEXT NULL');
CALL mj_safe_add_column('mj_lead_notes', 'status_from', '`status_from` VARCHAR(40) NULL');
CALL mj_safe_add_column('mj_lead_notes', 'status_to', '`status_to` VARCHAR(40) NULL');
CALL mj_safe_add_column('mj_lead_notes', 'followup_at', '`followup_at` DATETIME NULL');
CALL mj_safe_add_column('mj_lead_notes', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');


INSERT INTO mj_settings(setting_key, setting_value) VALUES
('user_leads_inbox_enabled','1'),
('global_lead_popup_enabled','1'),
('lead_browser_notification_enabled','1'),
('lead_email_notification_enabled','1'),
('v302_user_leads_inbox_global_enquiry_notifications','1')
ON DUPLICATE KEY UPDATE setting_value=VALUES(setting_value), updated_at=NOW();



-- ============================================================
-- Source: install/migration_v3.0.3_listing_company_enquiry_integration.sql
-- ============================================================

-- MJ Marketplace v3.0.3 Listing + Company Enquiry Integration
-- Adds display/enquiry-only contact forms for listing and company detail pages.
-- Business rule preserved: customer-to-seller payments are not processed by MJ Marketplace.

CREATE TABLE IF NOT EXISTS mj_listing_enquiries (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    listing_id BIGINT NOT NULL,
    seller_user_id BIGINT NOT NULL,
    company_id BIGINT NULL,
    customer_user_id BIGINT NULL,
    name VARCHAR(190) NOT NULL,
    email VARCHAR(190) NULL,
    phone VARCHAR(80) NULL,
    preferred_contact VARCHAR(40) NULL,
    message TEXT NOT NULL,
    status VARCHAR(40) NOT NULL DEFAULT 'new',
    ip_address VARCHAR(64) NULL,
    user_agent VARCHAR(255) NULL,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX(listing_id),
    INDEX(seller_user_id),
    INDEX(company_id),
    INDEX(customer_user_id),
    INDEX(status),
    INDEX(created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_company_enquiries (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    company_id BIGINT NOT NULL,
    owner_user_id BIGINT NOT NULL,
    customer_user_id BIGINT NULL,
    name VARCHAR(190) NOT NULL,
    email VARCHAR(190) NULL,
    phone VARCHAR(80) NULL,
    service_interested VARCHAR(190) NULL,
    preferred_contact VARCHAR(40) NULL,
    message TEXT NOT NULL,
    status VARCHAR(40) NOT NULL DEFAULT 'new',
    ip_address VARCHAR(64) NULL,
    user_agent VARCHAR(255) NULL,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX(company_id),
    INDEX(owner_user_id),
    INDEX(customer_user_id),
    INDEX(status),
    INDEX(created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_lead_notes (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    customer_id BIGINT NOT NULL,
    actor_user_id BIGINT NULL,
    actor_admin_id BIGINT NULL,
    note TEXT NULL,
    status_from VARCHAR(40) NULL,
    status_to VARCHAR(40) NULL,
    followup_at DATETIME NULL,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    INDEX(customer_id), INDEX(actor_user_id), INDEX(actor_admin_id), INDEX(created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_lead_notes (fresh installer safe).
CALL mj_safe_add_column('mj_lead_notes', 'customer_id', '`customer_id` BIGINT NOT NULL');
CALL mj_safe_add_column('mj_lead_notes', 'actor_user_id', '`actor_user_id` BIGINT NULL');
CALL mj_safe_add_column('mj_lead_notes', 'actor_admin_id', '`actor_admin_id` BIGINT NULL');
CALL mj_safe_add_column('mj_lead_notes', 'note', '`note` TEXT NULL');
CALL mj_safe_add_column('mj_lead_notes', 'status_from', '`status_from` VARCHAR(40) NULL');
CALL mj_safe_add_column('mj_lead_notes', 'status_to', '`status_to` VARCHAR(40) NULL');
CALL mj_safe_add_column('mj_lead_notes', 'followup_at', '`followup_at` DATETIME NULL');
CALL mj_safe_add_column('mj_lead_notes', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');


INSERT INTO mj_settings(setting_key, setting_value) VALUES
('listing_enquiry_enabled','1'),
('company_enquiry_enabled','1'),
('lead_email_notification_enabled','1'),
('global_lead_popup_enabled','1')
ON DUPLICATE KEY UPDATE setting_value=setting_value;



-- ============================================================
-- Source: install/migration_v3.0.4_full_from_v3.0.3_reconcile_self_contained.sql
-- ============================================================

-- MJ Marketplace v3.0.4 CRM + Notification + Admin Review Enhancement
-- Safe overlay migration. Run once after backing up DB.
-- Locked business rule reminder: customers never pay sellers through marketplace.

SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET time_zone = "+08:00";

CREATE TABLE IF NOT EXISTS `mj_notifications` (
  `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  `user_type` VARCHAR(20) NOT NULL DEFAULT 'user',
  `user_id` BIGINT UNSIGNED DEFAULT NULL,
  `title` VARCHAR(180) NOT NULL,
  `message` TEXT DEFAULT NULL,
  `type` VARCHAR(60) NOT NULL DEFAULT 'info',
  `link` VARCHAR(500) DEFAULT NULL,
  `is_read` TINYINT(1) NOT NULL DEFAULT 0,
  `read_at` DATETIME DEFAULT NULL,
  `meta_json` LONGTEXT DEFAULT NULL,
  `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_user_read_created` (`user_type`,`user_id`,`is_read`,`created_at`),
  KEY `idx_type_created` (`type`,`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_notifications (fresh installer safe).
CALL mj_safe_add_column('mj_notifications', 'id', '`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT');
CALL mj_safe_add_column('mj_notifications', 'user_type', '`user_type` VARCHAR(20) NOT NULL DEFAULT ''user''');
CALL mj_safe_add_column('mj_notifications', 'user_id', '`user_id` BIGINT UNSIGNED DEFAULT NULL');
CALL mj_safe_add_column('mj_notifications', 'title', '`title` VARCHAR(180) NOT NULL');
CALL mj_safe_add_column('mj_notifications', 'message', '`message` TEXT DEFAULT NULL');
CALL mj_safe_add_column('mj_notifications', 'type', '`type` VARCHAR(60) NOT NULL DEFAULT ''info''');
CALL mj_safe_add_column('mj_notifications', 'link', '`link` VARCHAR(500) DEFAULT NULL');
CALL mj_safe_add_column('mj_notifications', 'is_read', '`is_read` TINYINT(1) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_notifications', 'read_at', '`read_at` DATETIME DEFAULT NULL');
CALL mj_safe_add_column('mj_notifications', 'meta_json', '`meta_json` LONGTEXT DEFAULT NULL');
CALL mj_safe_add_column('mj_notifications', 'created_at', '`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP');


CREATE TABLE IF NOT EXISTS `seller_leads` (
  `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  `seller_user_id` BIGINT UNSIGNED NOT NULL DEFAULT 0,
  `source_type` VARCHAR(30) NOT NULL DEFAULT 'listing',
  `source_id` BIGINT UNSIGNED DEFAULT NULL,
  `source_title` VARCHAR(255) DEFAULT NULL,
  `country` VARCHAR(100) DEFAULT NULL,
  `customer_name` VARCHAR(160) DEFAULT NULL,
  `customer_email` VARCHAR(190) DEFAULT NULL,
  `customer_phone` VARCHAR(80) DEFAULT NULL,
  `customer_message` TEXT DEFAULT NULL,
  `status` VARCHAR(40) NOT NULL DEFAULT 'new',
  `priority` VARCHAR(20) NOT NULL DEFAULT 'normal',
  `followup_at` DATETIME DEFAULT NULL,
  `last_activity_at` DATETIME DEFAULT NULL,
  `is_read` TINYINT(1) NOT NULL DEFAULT 0,
  `ip_address` VARCHAR(80) DEFAULT NULL,
  `user_agent` VARCHAR(255) DEFAULT NULL,
  `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_seller_status_created` (`seller_user_id`,`status`,`created_at`),
  KEY `idx_source` (`source_type`,`source_id`),
  KEY `idx_customer_email` (`customer_email`),
  KEY `idx_followup` (`seller_user_id`,`followup_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS `seller_lead_activities` (
  `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  `lead_id` BIGINT UNSIGNED NOT NULL,
  `user_id` BIGINT UNSIGNED DEFAULT NULL,
  `user_type` VARCHAR(20) NOT NULL DEFAULT 'user',
  `activity_type` VARCHAR(40) NOT NULL DEFAULT 'note',
  `note` TEXT DEFAULT NULL,
  `old_status` VARCHAR(40) DEFAULT NULL,
  `new_status` VARCHAR(40) DEFAULT NULL,
  `followup_at` DATETIME DEFAULT NULL,
  `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_lead_created` (`lead_id`,`created_at`),
  KEY `idx_user_created` (`user_type`,`user_id`,`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS `mj_admin_review_queue` (
  `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  `item_type` VARCHAR(30) NOT NULL,
  `item_id` BIGINT UNSIGNED NOT NULL,
  `user_id` BIGINT UNSIGNED NOT NULL DEFAULT 0,
  `status` VARCHAR(30) NOT NULL DEFAULT 'pending',
  `old_data_json` LONGTEXT DEFAULT NULL,
  `new_data_json` LONGTEXT NOT NULL,
  `reject_reason` TEXT DEFAULT NULL,
  `reviewed_by` BIGINT UNSIGNED DEFAULT NULL,
  `reviewed_at` DATETIME DEFAULT NULL,
  `submitted_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_status_submitted` (`status`,`submitted_at`),
  KEY `idx_item` (`item_type`,`item_id`),
  KEY `idx_user_status` (`user_id`,`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS `mj_notification_settings` (
  `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  `user_type` VARCHAR(20) NOT NULL DEFAULT 'user',
  `user_id` BIGINT UNSIGNED NOT NULL DEFAULT 0,
  `browser_enabled` TINYINT(1) NOT NULL DEFAULT 1,
  `email_enabled` TINYINT(1) NOT NULL DEFAULT 1,
  `lead_enabled` TINYINT(1) NOT NULL DEFAULT 1,
  `payment_enabled` TINYINT(1) NOT NULL DEFAULT 1,
  `review_enabled` TINYINT(1) NOT NULL DEFAULT 1,
  `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uniq_user_setting` (`user_type`,`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_notification_settings (fresh installer safe).
CALL mj_safe_add_column('mj_notification_settings', 'id', '`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT');
CALL mj_safe_add_column('mj_notification_settings', 'user_type', '`user_type` VARCHAR(20) NOT NULL DEFAULT ''user''');
CALL mj_safe_add_column('mj_notification_settings', 'user_id', '`user_id` BIGINT UNSIGNED NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_notification_settings', 'browser_enabled', '`browser_enabled` TINYINT(1) NOT NULL DEFAULT 1');
CALL mj_safe_add_column('mj_notification_settings', 'email_enabled', '`email_enabled` TINYINT(1) NOT NULL DEFAULT 1');
CALL mj_safe_add_column('mj_notification_settings', 'lead_enabled', '`lead_enabled` TINYINT(1) NOT NULL DEFAULT 1');
CALL mj_safe_add_column('mj_notification_settings', 'payment_enabled', '`payment_enabled` TINYINT(1) NOT NULL DEFAULT 1');
CALL mj_safe_add_column('mj_notification_settings', 'review_enabled', '`review_enabled` TINYINT(1) NOT NULL DEFAULT 1');
CALL mj_safe_add_column('mj_notification_settings', 'created_at', '`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP');
CALL mj_safe_add_column('mj_notification_settings', 'updated_at', '`updated_at` DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP');


DROP PROCEDURE IF EXISTS mj_v304_add_column_if_missing;
DROP PROCEDURE IF EXISTS mj_v304_add_index_if_missing;

DELIMITER $$
CREATE PROCEDURE mj_v304_add_column_if_missing(IN p_table VARCHAR(64), IN p_column VARCHAR(64), IN p_definition TEXT)
BEGIN
  IF NOT EXISTS (
    SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS
    WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = p_table AND COLUMN_NAME = p_column
  ) THEN
    SET @ddl = CONCAT('ALTER TABLE `', p_table, '` ADD COLUMN `', p_column, '` ', p_definition);
    PREPARE stmt FROM @ddl;
    EXECUTE stmt;
    DEALLOCATE PREPARE stmt;
  END IF;
END$$

CREATE PROCEDURE mj_v304_add_index_if_missing(IN p_table VARCHAR(64), IN p_index VARCHAR(64), IN p_definition TEXT)
BEGIN
  IF NOT EXISTS (
    SELECT 1 FROM INFORMATION_SCHEMA.STATISTICS
    WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = p_table AND INDEX_NAME = p_index
  ) THEN
    SET @ddl = CONCAT('ALTER TABLE `', p_table, '` ADD INDEX `', p_index, '` ', p_definition);
    PREPARE stmt FROM @ddl;
    EXECUTE stmt;
    DEALLOCATE PREPARE stmt;
  END IF;
END$$
DELIMITER ;

CALL mj_v304_add_column_if_missing('seller_leads','seller_user_id','BIGINT UNSIGNED NOT NULL DEFAULT 0');
CALL mj_v304_add_column_if_missing('seller_leads','source_type','VARCHAR(30) NOT NULL DEFAULT ''listing''');
CALL mj_v304_add_column_if_missing('seller_leads','source_id','BIGINT UNSIGNED DEFAULT NULL');
CALL mj_v304_add_column_if_missing('seller_leads','source_title','VARCHAR(255) DEFAULT NULL');
CALL mj_v304_add_column_if_missing('seller_leads','country','VARCHAR(100) DEFAULT NULL');
CALL mj_v304_add_column_if_missing('seller_leads','customer_name','VARCHAR(160) DEFAULT NULL');
CALL mj_v304_add_column_if_missing('seller_leads','customer_email','VARCHAR(190) DEFAULT NULL');
CALL mj_v304_add_column_if_missing('seller_leads','customer_phone','VARCHAR(80) DEFAULT NULL');
CALL mj_v304_add_column_if_missing('seller_leads','customer_message','TEXT DEFAULT NULL');
CALL mj_v304_add_column_if_missing('seller_leads','status','VARCHAR(40) NOT NULL DEFAULT ''new''');
CALL mj_v304_add_column_if_missing('seller_leads','priority','VARCHAR(20) NOT NULL DEFAULT ''normal''');
CALL mj_v304_add_column_if_missing('seller_leads','followup_at','DATETIME DEFAULT NULL');
CALL mj_v304_add_column_if_missing('seller_leads','last_activity_at','DATETIME DEFAULT NULL');
CALL mj_v304_add_column_if_missing('seller_leads','is_read','TINYINT(1) NOT NULL DEFAULT 0');
CALL mj_v304_add_column_if_missing('seller_leads','ip_address','VARCHAR(80) DEFAULT NULL');
CALL mj_v304_add_column_if_missing('seller_leads','user_agent','VARCHAR(255) DEFAULT NULL');
CALL mj_v304_add_column_if_missing('seller_leads','created_at','DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP');
CALL mj_v304_add_column_if_missing('seller_leads','updated_at','DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP');

CALL mj_v304_add_index_if_missing('seller_leads','idx_seller_status_created','(`seller_user_id`,`status`,`created_at`)');
CALL mj_v304_add_index_if_missing('seller_leads','idx_source','(`source_type`,`source_id`)');
CALL mj_v304_add_index_if_missing('seller_leads','idx_customer_email','(`customer_email`)');
CALL mj_v304_add_index_if_missing('seller_leads','idx_followup','(`seller_user_id`,`followup_at`)');

DROP PROCEDURE IF EXISTS mj_v304_add_column_if_missing;
DROP PROCEDURE IF EXISTS mj_v304_add_index_if_missing;


-- Existing v3.0.3 CRM repair columns (safe only if table exists).

DROP PROCEDURE IF EXISTS mj_v304_reconcile_add_col;
DELIMITER $$
CREATE PROCEDURE mj_v304_reconcile_add_col(IN p_table VARCHAR(64), IN p_column VARCHAR(64), IN p_definition TEXT)
BEGIN
  IF EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = p_table)
     AND NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = p_table AND COLUMN_NAME = p_column) THEN
    SET @ddl = CONCAT('ALTER TABLE `', p_table, '` ADD COLUMN `', p_column, '` ', p_definition);
    PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
  END IF;
END$$
DELIMITER ;

CALL mj_v304_reconcile_add_col('mj_customers','source_type','VARCHAR(40) DEFAULT NULL');
CALL mj_v304_reconcile_add_col('mj_customers','source_id','BIGINT UNSIGNED DEFAULT NULL');
CALL mj_v304_reconcile_add_col('mj_customers','source_label','VARCHAR(255) DEFAULT NULL');
CALL mj_v304_reconcile_add_col('mj_customers','private_note','TEXT DEFAULT NULL');
CALL mj_v304_reconcile_add_col('mj_customers','next_followup_at','DATETIME DEFAULT NULL');
CALL mj_v304_reconcile_add_col('mj_customers','last_activity_at','DATETIME DEFAULT NULL');
CALL mj_v304_reconcile_add_col('mj_customers','updated_at','DATETIME DEFAULT NULL');

DROP PROCEDURE IF EXISTS mj_v304_reconcile_add_col;



-- ============================================================
-- Source: install/hotfix_v3.0.4.1_user_deal_leads_notification_location_search.sql
-- ============================================================

-- MJ Marketplace v3.0.4.1 Stable Hotfix
-- User deal/leads access reset + notification compatibility + searchable location support.
-- No v3.0.5 city importer. No customer-to-seller payment.

SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET time_zone = "+08:00";

CREATE TABLE IF NOT EXISTS `mj_notifications` (
  `id` BIGINT NOT NULL AUTO_INCREMENT,
  `user_id` BIGINT NULL,
  `admin_id` BIGINT NULL,
  `user_type` VARCHAR(20) NULL,
  `title` VARCHAR(255) NOT NULL,
  `message` TEXT NULL,
  `body` TEXT NULL,
  `type` VARCHAR(80) NULL,
  `priority` VARCHAR(40) NOT NULL DEFAULT 'normal',
  `link` VARCHAR(500) NULL,
  `action_url` VARCHAR(500) NULL,
  `url` VARCHAR(500) NULL,
  `is_read` TINYINT(1) NOT NULL DEFAULT 0,
  `is_popup_shown` TINYINT(1) NOT NULL DEFAULT 0,
  `popup_shown_at` DATETIME NULL,
  `read_at` DATETIME NULL,
  `meta_json` LONGTEXT NULL,
  `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_user_read_created` (`user_id`,`is_read`,`created_at`),
  KEY `idx_admin_read_created` (`admin_id`,`is_read`,`created_at`),
  KEY `idx_type_created` (`type`,`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE mj_notifications (fresh installer safe).
CALL mj_safe_add_column('mj_notifications', 'id', '`id` BIGINT NOT NULL AUTO_INCREMENT');
CALL mj_safe_add_column('mj_notifications', 'user_id', '`user_id` BIGINT NULL');
CALL mj_safe_add_column('mj_notifications', 'admin_id', '`admin_id` BIGINT NULL');
CALL mj_safe_add_column('mj_notifications', 'user_type', '`user_type` VARCHAR(20) NULL');
CALL mj_safe_add_column('mj_notifications', 'title', '`title` VARCHAR(255) NOT NULL');
CALL mj_safe_add_column('mj_notifications', 'message', '`message` TEXT NULL');
CALL mj_safe_add_column('mj_notifications', 'body', '`body` TEXT NULL');
CALL mj_safe_add_column('mj_notifications', 'type', '`type` VARCHAR(80) NULL');
CALL mj_safe_add_column('mj_notifications', 'priority', '`priority` VARCHAR(40) NOT NULL DEFAULT ''normal''');
CALL mj_safe_add_column('mj_notifications', 'link', '`link` VARCHAR(500) NULL');
CALL mj_safe_add_column('mj_notifications', 'action_url', '`action_url` VARCHAR(500) NULL');
CALL mj_safe_add_column('mj_notifications', 'url', '`url` VARCHAR(500) NULL');
CALL mj_safe_add_column('mj_notifications', 'is_read', '`is_read` TINYINT(1) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_notifications', 'is_popup_shown', '`is_popup_shown` TINYINT(1) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_notifications', 'popup_shown_at', '`popup_shown_at` DATETIME NULL');
CALL mj_safe_add_column('mj_notifications', 'read_at', '`read_at` DATETIME NULL');
CALL mj_safe_add_column('mj_notifications', 'meta_json', '`meta_json` LONGTEXT NULL');
CALL mj_safe_add_column('mj_notifications', 'created_at', '`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP');


CREATE TABLE IF NOT EXISTS `mj_location_pending` (
  `id` BIGINT NOT NULL AUTO_INCREMENT,
  `country_id` INT NULL,
  `state_id` BIGINT NULL,
  `city_id` BIGINT NULL,
  `country_name` VARCHAR(180) NULL,
  `state_name` VARCHAR(180) NULL,
  `city_name` VARCHAR(180) NOT NULL,
  `city_slug` VARCHAR(220) NOT NULL,
  `latitude` DECIMAL(12,8) NULL,
  `longitude` DECIMAL(12,8) NULL,
  `timezone` VARCHAR(120) NULL,
  `source` VARCHAR(40) NOT NULL DEFAULT 'manual',
  `status` VARCHAR(40) NOT NULL DEFAULT 'pending',
  `created_by` BIGINT NULL,
  `created_user_type` VARCHAR(20) NULL,
  `related_type` VARCHAR(40) NULL,
  `related_id` BIGINT NULL,
  `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
  `updated_at` DATETIME NULL,
  PRIMARY KEY (`id`),
  KEY `idx_pending_status` (`status`),
  KEY `idx_pending_country` (`country_id`),
  KEY `idx_pending_city` (`city_slug`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS `seller_leads` (
  `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  `seller_user_id` BIGINT UNSIGNED NOT NULL DEFAULT 0,
  `source_type` VARCHAR(30) NOT NULL DEFAULT 'listing',
  `source_id` BIGINT UNSIGNED DEFAULT NULL,
  `source_title` VARCHAR(255) DEFAULT NULL,
  `country` VARCHAR(100) DEFAULT NULL,
  `customer_name` VARCHAR(160) DEFAULT NULL,
  `customer_email` VARCHAR(190) DEFAULT NULL,
  `customer_phone` VARCHAR(80) DEFAULT NULL,
  `customer_message` TEXT DEFAULT NULL,
  `status` VARCHAR(40) NOT NULL DEFAULT 'new',
  `priority` VARCHAR(20) NOT NULL DEFAULT 'normal',
  `followup_at` DATETIME DEFAULT NULL,
  `last_activity_at` DATETIME DEFAULT NULL,
  `is_read` TINYINT(1) NOT NULL DEFAULT 0,
  `ip_address` VARCHAR(80) DEFAULT NULL,
  `user_agent` VARCHAR(255) DEFAULT NULL,
  `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` DATETIME DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_seller_status_created` (`seller_user_id`,`status`,`created_at`),
  KEY `idx_source` (`source_type`,`source_id`),
  KEY `idx_customer_email` (`customer_email`),
  KEY `idx_followup` (`seller_user_id`,`followup_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE seller_leads (fresh installer safe).
CALL mj_safe_add_column('seller_leads', 'id', '`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT');
CALL mj_safe_add_column('seller_leads', 'seller_user_id', '`seller_user_id` BIGINT UNSIGNED NOT NULL DEFAULT 0');
CALL mj_safe_add_column('seller_leads', 'source_type', '`source_type` VARCHAR(30) NOT NULL DEFAULT ''listing''');
CALL mj_safe_add_column('seller_leads', 'source_id', '`source_id` BIGINT UNSIGNED DEFAULT NULL');
CALL mj_safe_add_column('seller_leads', 'source_title', '`source_title` VARCHAR(255) DEFAULT NULL');
CALL mj_safe_add_column('seller_leads', 'country', '`country` VARCHAR(100) DEFAULT NULL');
CALL mj_safe_add_column('seller_leads', 'customer_name', '`customer_name` VARCHAR(160) DEFAULT NULL');
CALL mj_safe_add_column('seller_leads', 'customer_email', '`customer_email` VARCHAR(190) DEFAULT NULL');
CALL mj_safe_add_column('seller_leads', 'customer_phone', '`customer_phone` VARCHAR(80) DEFAULT NULL');
CALL mj_safe_add_column('seller_leads', 'customer_message', '`customer_message` TEXT DEFAULT NULL');
CALL mj_safe_add_column('seller_leads', 'status', '`status` VARCHAR(40) NOT NULL DEFAULT ''new''');
CALL mj_safe_add_column('seller_leads', 'priority', '`priority` VARCHAR(20) NOT NULL DEFAULT ''normal''');
CALL mj_safe_add_column('seller_leads', 'followup_at', '`followup_at` DATETIME DEFAULT NULL');
CALL mj_safe_add_column('seller_leads', 'last_activity_at', '`last_activity_at` DATETIME DEFAULT NULL');
CALL mj_safe_add_column('seller_leads', 'is_read', '`is_read` TINYINT(1) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('seller_leads', 'ip_address', '`ip_address` VARCHAR(80) DEFAULT NULL');
CALL mj_safe_add_column('seller_leads', 'user_agent', '`user_agent` VARCHAR(255) DEFAULT NULL');
CALL mj_safe_add_column('seller_leads', 'created_at', '`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP');
CALL mj_safe_add_column('seller_leads', 'updated_at', '`updated_at` DATETIME DEFAULT NULL');


CREATE TABLE IF NOT EXISTS `seller_lead_activities` (
  `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  `lead_id` BIGINT UNSIGNED NOT NULL,
  `user_id` BIGINT UNSIGNED DEFAULT NULL,
  `user_type` VARCHAR(20) NOT NULL DEFAULT 'user',
  `activity_type` VARCHAR(40) NOT NULL DEFAULT 'note',
  `note` TEXT DEFAULT NULL,
  `old_status` VARCHAR(40) DEFAULT NULL,
  `new_status` VARCHAR(40) DEFAULT NULL,
  `followup_at` DATETIME DEFAULT NULL,
  `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_lead_created` (`lead_id`,`created_at`),
  KEY `idx_user_created` (`user_type`,`user_id`,`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Auto repair columns for duplicate CREATE TABLE seller_lead_activities (fresh installer safe).
CALL mj_safe_add_column('seller_lead_activities', 'id', '`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT');
CALL mj_safe_add_column('seller_lead_activities', 'lead_id', '`lead_id` BIGINT UNSIGNED NOT NULL');
CALL mj_safe_add_column('seller_lead_activities', 'user_id', '`user_id` BIGINT UNSIGNED DEFAULT NULL');
CALL mj_safe_add_column('seller_lead_activities', 'user_type', '`user_type` VARCHAR(20) NOT NULL DEFAULT ''user''');
CALL mj_safe_add_column('seller_lead_activities', 'activity_type', '`activity_type` VARCHAR(40) NOT NULL DEFAULT ''note''');
CALL mj_safe_add_column('seller_lead_activities', 'note', '`note` TEXT DEFAULT NULL');
CALL mj_safe_add_column('seller_lead_activities', 'old_status', '`old_status` VARCHAR(40) DEFAULT NULL');
CALL mj_safe_add_column('seller_lead_activities', 'new_status', '`new_status` VARCHAR(40) DEFAULT NULL');
CALL mj_safe_add_column('seller_lead_activities', 'followup_at', '`followup_at` DATETIME DEFAULT NULL');
CALL mj_safe_add_column('seller_lead_activities', 'created_at', '`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP');


DROP PROCEDURE IF EXISTS mj_v3041_add_column_if_missing;
DROP PROCEDURE IF EXISTS mj_v3041_add_index_if_missing;
DELIMITER $$
CREATE PROCEDURE mj_v3041_add_column_if_missing(IN p_table VARCHAR(64), IN p_column VARCHAR(64), IN p_definition TEXT)
BEGIN
  IF EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = p_table)
     AND NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = p_table AND COLUMN_NAME = p_column) THEN
    SET @ddl = CONCAT('ALTER TABLE `', p_table, '` ADD COLUMN `', p_column, '` ', p_definition);
    PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
  END IF;
END$$
CREATE PROCEDURE mj_v3041_add_index_if_missing(IN p_table VARCHAR(64), IN p_index VARCHAR(64), IN p_definition TEXT)
BEGIN
  IF EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = p_table)
     AND NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = p_table AND INDEX_NAME = p_index) THEN
    SET @ddl = CONCAT('ALTER TABLE `', p_table, '` ADD INDEX `', p_index, '` ', p_definition);
    PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
  END IF;
END$$
DELIMITER ;

CALL mj_v3041_add_column_if_missing('mj_notifications','user_type','VARCHAR(20) NULL');
CALL mj_v3041_add_column_if_missing('mj_notifications','admin_id','BIGINT NULL');
CALL mj_v3041_add_column_if_missing('mj_notifications','message','TEXT NULL');
CALL mj_v3041_add_column_if_missing('mj_notifications','body','TEXT NULL');
CALL mj_v3041_add_column_if_missing('mj_notifications','type','VARCHAR(80) NULL');
CALL mj_v3041_add_column_if_missing('mj_notifications','priority','VARCHAR(40) NOT NULL DEFAULT ''normal''');
CALL mj_v3041_add_column_if_missing('mj_notifications','link','VARCHAR(500) NULL');
CALL mj_v3041_add_column_if_missing('mj_notifications','action_url','VARCHAR(500) NULL');
CALL mj_v3041_add_column_if_missing('mj_notifications','url','VARCHAR(500) NULL');
CALL mj_v3041_add_column_if_missing('mj_notifications','is_popup_shown','TINYINT(1) NOT NULL DEFAULT 0');
CALL mj_v3041_add_column_if_missing('mj_notifications','popup_shown_at','DATETIME NULL');
CALL mj_v3041_add_column_if_missing('mj_notifications','read_at','DATETIME NULL');
CALL mj_v3041_add_column_if_missing('mj_notifications','meta_json','LONGTEXT NULL');

CALL mj_v3041_add_column_if_missing('mj_users','allow_deals','TINYINT(1) NOT NULL DEFAULT 1');
CALL mj_v3041_add_column_if_missing('mj_users','allow_leads','TINYINT(1) NOT NULL DEFAULT 1');
CALL mj_v3041_add_column_if_missing('mj_users','allow_crm','TINYINT(1) NOT NULL DEFAULT 1');

CALL mj_v3041_add_column_if_missing('mj_states','slug','VARCHAR(200) NULL');
CALL mj_v3041_add_column_if_missing('mj_states','is_active','TINYINT(1) NOT NULL DEFAULT 1');
CALL mj_v3041_add_column_if_missing('mj_cities','slug','VARCHAR(200) NULL');
CALL mj_v3041_add_column_if_missing('mj_cities','latitude','DECIMAL(12,8) NULL');
CALL mj_v3041_add_column_if_missing('mj_cities','longitude','DECIMAL(12,8) NULL');
CALL mj_v3041_add_column_if_missing('mj_cities','timezone','VARCHAR(120) NULL');
CALL mj_v3041_add_column_if_missing('mj_cities','is_active','TINYINT(1) NOT NULL DEFAULT 1');

CALL mj_v3041_add_index_if_missing('mj_countries','idx_v3041_country_search','(`is_active`,`name`)');
CALL mj_v3041_add_index_if_missing('mj_states','idx_v3041_state_search','(`country_id`,`is_active`,`name`)');
CALL mj_v3041_add_index_if_missing('mj_cities','idx_v3041_city_search','(`country_id`,`state_id`,`is_active`,`name`)');
CALL mj_v3041_add_index_if_missing('mj_notifications','idx_v3041_notify_user','(`user_id`,`is_read`,`created_at`)');
CALL mj_v3041_add_index_if_missing('mj_notifications','idx_v3041_notify_admin','(`admin_id`,`is_read`,`created_at`)');

-- Allow all existing users/sellers to access deals, CRM and leads.
UPDATE `mj_users` SET `allow_deals`=1, `allow_leads`=1, `allow_crm`=1;

INSERT INTO `mj_site_modules` (`module_code`,`module_name`,`description`,`is_enabled`) VALUES
('deals','Deals','Display-only deal listings with enquiry/contact, no customer checkout',1),
('crm','CRM / Leads','Seller lead inbox and customer follow-up',1),
('notifications','Notifications','Global user/admin notifications for leads, contacts, chats, payments and reviews',1),
('state_city','State / City Search','Searchable country/state/city fields with safe live city lookup',1)
ON DUPLICATE KEY UPDATE `is_enabled`=1;

-- Repair missing seller owner IDs where source_id points directly to active item IDs.
UPDATE seller_leads sl INNER JOIN mj_deals d ON sl.source_type IN ('deal','deal_enquiry') AND sl.source_id=d.id
SET sl.seller_user_id=d.user_id
WHERE (sl.seller_user_id IS NULL OR sl.seller_user_id=0) AND d.user_id IS NOT NULL;

UPDATE seller_leads sl INNER JOIN mj_listings l ON sl.source_type IN ('listing','listing_enquiry') AND sl.source_id=l.id
SET sl.seller_user_id=l.user_id
WHERE (sl.seller_user_id IS NULL OR sl.seller_user_id=0) AND l.user_id IS NOT NULL;

UPDATE seller_leads sl INNER JOIN mj_companies c ON sl.source_type IN ('company','company_enquiry') AND sl.source_id=c.id
SET sl.seller_user_id=c.user_id
WHERE (sl.seller_user_id IS NULL OR sl.seller_user_id=0) AND c.user_id IS NOT NULL;

DROP PROCEDURE IF EXISTS mj_v3041_add_column_if_missing;
DROP PROCEDURE IF EXISTS mj_v3041_add_index_if_missing;



-- ============================================================
-- Fresh install complete: MJ Marketplace v3.0.4.1 stable.
-- Run install/sample_data.sql separately only if you want demo users/listings.
-- ============================================================
SET FOREIGN_KEY_CHECKS = 1;


-- ============================================================
-- MJ Marketplace v3.0.4.2.4 full audit repair
-- Adds missing lead compatibility tables and migration tracker.
-- Safe for fresh install and existing update. Does not delete data.
-- ============================================================
CREATE TABLE IF NOT EXISTS `mj_schema_migrations` (
  `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  `version` VARCHAR(60) NOT NULL,
  `migration_file` VARCHAR(255) NOT NULL,
  `status` VARCHAR(40) NOT NULL DEFAULT 'completed',
  `started_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
  `completed_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
  `error_message` TEXT DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uniq_version_file` (`version`,`migration_file`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS `seller_leads` (
  `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  `seller_user_id` BIGINT UNSIGNED NOT NULL DEFAULT 0,
  `source_type` VARCHAR(30) NOT NULL DEFAULT 'listing',
  `source_id` BIGINT UNSIGNED DEFAULT NULL,
  `source_title` VARCHAR(255) DEFAULT NULL,
  `country` VARCHAR(100) DEFAULT NULL,
  `customer_name` VARCHAR(160) DEFAULT NULL,
  `customer_email` VARCHAR(190) DEFAULT NULL,
  `customer_phone` VARCHAR(80) DEFAULT NULL,
  `customer_message` TEXT DEFAULT NULL,
  `status` VARCHAR(40) NOT NULL DEFAULT 'new',
  `priority` VARCHAR(20) NOT NULL DEFAULT 'normal',
  `followup_at` DATETIME DEFAULT NULL,
  `last_activity_at` DATETIME DEFAULT NULL,
  `is_read` TINYINT(1) NOT NULL DEFAULT 0,
  `ip_address` VARCHAR(80) DEFAULT NULL,
  `user_agent` VARCHAR(255) DEFAULT NULL,
  `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_seller_status_created` (`seller_user_id`,`status`,`created_at`),
  KEY `idx_source` (`source_type`,`source_id`),
  KEY `idx_customer_email` (`customer_email`),
  KEY `idx_followup` (`seller_user_id`,`followup_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;CALL mj_safe_add_column('seller_leads', 'seller_user_id', '`seller_user_id` BIGINT UNSIGNED NOT NULL DEFAULT 0');
CALL mj_safe_add_column('seller_leads', 'source_type', '`source_type` VARCHAR(30) NOT NULL DEFAULT ''listing''');
CALL mj_safe_add_column('seller_leads', 'source_id', '`source_id` BIGINT UNSIGNED DEFAULT NULL');
CALL mj_safe_add_column('seller_leads', 'source_title', '`source_title` VARCHAR(255) DEFAULT NULL');
CALL mj_safe_add_column('seller_leads', 'country', '`country` VARCHAR(100) DEFAULT NULL');
CALL mj_safe_add_column('seller_leads', 'customer_name', '`customer_name` VARCHAR(160) DEFAULT NULL');
CALL mj_safe_add_column('seller_leads', 'customer_email', '`customer_email` VARCHAR(190) DEFAULT NULL');
CALL mj_safe_add_column('seller_leads', 'customer_phone', '`customer_phone` VARCHAR(80) DEFAULT NULL');
CALL mj_safe_add_column('seller_leads', 'customer_message', '`customer_message` TEXT DEFAULT NULL');
CALL mj_safe_add_column('seller_leads', 'status', '`status` VARCHAR(40) NOT NULL DEFAULT ''new''');
CALL mj_safe_add_column('seller_leads', 'priority', '`priority` VARCHAR(20) NOT NULL DEFAULT ''normal''');
CALL mj_safe_add_column('seller_leads', 'followup_at', '`followup_at` DATETIME DEFAULT NULL');
CALL mj_safe_add_column('seller_leads', 'last_activity_at', '`last_activity_at` DATETIME DEFAULT NULL');
CALL mj_safe_add_column('seller_leads', 'is_read', '`is_read` TINYINT(1) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('seller_leads', 'ip_address', '`ip_address` VARCHAR(80) DEFAULT NULL');
CALL mj_safe_add_column('seller_leads', 'user_agent', '`user_agent` VARCHAR(255) DEFAULT NULL');
CALL mj_safe_add_column('seller_leads', 'created_at', '`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP');
CALL mj_safe_add_column('seller_leads', 'updated_at', '`updated_at` DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP');


CREATE TABLE IF NOT EXISTS `seller_lead_activities` (
  `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  `lead_id` BIGINT UNSIGNED NOT NULL,
  `user_id` BIGINT UNSIGNED DEFAULT NULL,
  `user_type` VARCHAR(20) NOT NULL DEFAULT 'user',
  `activity_type` VARCHAR(40) NOT NULL DEFAULT 'note',
  `note` TEXT DEFAULT NULL,
  `old_status` VARCHAR(40) DEFAULT NULL,
  `new_status` VARCHAR(40) DEFAULT NULL,
  `followup_at` DATETIME DEFAULT NULL,
  `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_lead_created` (`lead_id`,`created_at`),
  KEY `idx_user_created` (`user_type`,`user_id`,`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS `mj_seller_leads` (
  `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  `lead_no` VARCHAR(60) DEFAULT NULL,
  `seller_user_id` BIGINT UNSIGNED DEFAULT NULL,
  `user_id` BIGINT UNSIGNED DEFAULT NULL,
  `seller_id` BIGINT UNSIGNED DEFAULT NULL,
  `customer_user_id` BIGINT UNSIGNED DEFAULT NULL,
  `customer_id` BIGINT UNSIGNED DEFAULT NULL,
  `lead_type` VARCHAR(40) NOT NULL DEFAULT 'general',
  `source_type` VARCHAR(40) DEFAULT NULL,
  `source_id` BIGINT UNSIGNED DEFAULT NULL,
  `item_type` VARCHAR(40) DEFAULT NULL,
  `item_id` BIGINT UNSIGNED DEFAULT NULL,
  `listing_id` BIGINT UNSIGNED DEFAULT NULL,
  `company_id` BIGINT UNSIGNED DEFAULT NULL,
  `deal_id` BIGINT UNSIGNED DEFAULT NULL,
  `title` VARCHAR(255) DEFAULT NULL,
  `subject` VARCHAR(255) DEFAULT NULL,
  `customer_name` VARCHAR(190) DEFAULT NULL,
  `customer_email` VARCHAR(190) DEFAULT NULL,
  `customer_phone` VARCHAR(80) DEFAULT NULL,
  `customer_whatsapp` VARCHAR(80) DEFAULT NULL,
  `customer_country` VARCHAR(120) DEFAULT NULL,
  `customer_state` VARCHAR(120) DEFAULT NULL,
  `customer_city` VARCHAR(120) DEFAULT NULL,
  `message` TEXT DEFAULT NULL,
  `last_note` TEXT DEFAULT NULL,
  `admin_note` TEXT DEFAULT NULL,
  `status` VARCHAR(40) NOT NULL DEFAULT 'new',
  `priority` VARCHAR(40) NOT NULL DEFAULT 'normal',
  `is_read` TINYINT(1) NOT NULL DEFAULT 0,
  `read_at` DATETIME DEFAULT NULL,
  `follow_up_date` DATE DEFAULT NULL,
  `follow_up_at` DATETIME DEFAULT NULL,
  `last_activity_at` DATETIME DEFAULT NULL,
  `converted_at` DATETIME DEFAULT NULL,
  `closed_at` DATETIME DEFAULT NULL,
  `ip_address` VARCHAR(80) DEFAULT NULL,
  `user_agent` VARCHAR(255) 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_seller_status` (`seller_user_id`,`status`),
  KEY `idx_user_status` (`user_id`,`status`),
  KEY `idx_item` (`item_type`,`item_id`),
  KEY `idx_listing` (`listing_id`),
  KEY `idx_company` (`company_id`),
  KEY `idx_deal` (`deal_id`),
  KEY `idx_created` (`created_at`),
  KEY `idx_followup` (`follow_up_date`),
  KEY `idx_email` (`customer_email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;CALL mj_safe_add_column('mj_seller_leads', 'lead_no', '`lead_no` VARCHAR(60) DEFAULT NULL');
CALL mj_safe_add_column('mj_seller_leads', 'seller_user_id', '`seller_user_id` BIGINT UNSIGNED DEFAULT NULL');
CALL mj_safe_add_column('mj_seller_leads', 'user_id', '`user_id` BIGINT UNSIGNED DEFAULT NULL');
CALL mj_safe_add_column('mj_seller_leads', 'seller_id', '`seller_id` BIGINT UNSIGNED DEFAULT NULL');
CALL mj_safe_add_column('mj_seller_leads', 'customer_user_id', '`customer_user_id` BIGINT UNSIGNED DEFAULT NULL');
CALL mj_safe_add_column('mj_seller_leads', 'customer_id', '`customer_id` BIGINT UNSIGNED DEFAULT NULL');
CALL mj_safe_add_column('mj_seller_leads', 'lead_type', '`lead_type` VARCHAR(40) NOT NULL DEFAULT ''general''');
CALL mj_safe_add_column('mj_seller_leads', 'source_type', '`source_type` VARCHAR(40) DEFAULT NULL');
CALL mj_safe_add_column('mj_seller_leads', 'source_id', '`source_id` BIGINT UNSIGNED DEFAULT NULL');
CALL mj_safe_add_column('mj_seller_leads', 'item_type', '`item_type` VARCHAR(40) DEFAULT NULL');
CALL mj_safe_add_column('mj_seller_leads', 'item_id', '`item_id` BIGINT UNSIGNED DEFAULT NULL');
CALL mj_safe_add_column('mj_seller_leads', 'listing_id', '`listing_id` BIGINT UNSIGNED DEFAULT NULL');
CALL mj_safe_add_column('mj_seller_leads', 'company_id', '`company_id` BIGINT UNSIGNED DEFAULT NULL');
CALL mj_safe_add_column('mj_seller_leads', 'deal_id', '`deal_id` BIGINT UNSIGNED DEFAULT NULL');
CALL mj_safe_add_column('mj_seller_leads', 'title', '`title` VARCHAR(255) DEFAULT NULL');
CALL mj_safe_add_column('mj_seller_leads', 'subject', '`subject` VARCHAR(255) DEFAULT NULL');
CALL mj_safe_add_column('mj_seller_leads', 'customer_name', '`customer_name` VARCHAR(190) DEFAULT NULL');
CALL mj_safe_add_column('mj_seller_leads', 'customer_email', '`customer_email` VARCHAR(190) DEFAULT NULL');
CALL mj_safe_add_column('mj_seller_leads', 'customer_phone', '`customer_phone` VARCHAR(80) DEFAULT NULL');
CALL mj_safe_add_column('mj_seller_leads', 'customer_whatsapp', '`customer_whatsapp` VARCHAR(80) DEFAULT NULL');
CALL mj_safe_add_column('mj_seller_leads', 'customer_country', '`customer_country` VARCHAR(120) DEFAULT NULL');
CALL mj_safe_add_column('mj_seller_leads', 'customer_state', '`customer_state` VARCHAR(120) DEFAULT NULL');
CALL mj_safe_add_column('mj_seller_leads', 'customer_city', '`customer_city` VARCHAR(120) DEFAULT NULL');
CALL mj_safe_add_column('mj_seller_leads', 'message', '`message` TEXT DEFAULT NULL');
CALL mj_safe_add_column('mj_seller_leads', 'last_note', '`last_note` TEXT DEFAULT NULL');
CALL mj_safe_add_column('mj_seller_leads', 'admin_note', '`admin_note` TEXT DEFAULT NULL');
CALL mj_safe_add_column('mj_seller_leads', 'status', '`status` VARCHAR(40) NOT NULL DEFAULT ''new''');
CALL mj_safe_add_column('mj_seller_leads', 'priority', '`priority` VARCHAR(40) NOT NULL DEFAULT ''normal''');
CALL mj_safe_add_column('mj_seller_leads', 'is_read', '`is_read` TINYINT(1) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_seller_leads', 'read_at', '`read_at` DATETIME DEFAULT NULL');
CALL mj_safe_add_column('mj_seller_leads', 'follow_up_date', '`follow_up_date` DATE DEFAULT NULL');
CALL mj_safe_add_column('mj_seller_leads', 'follow_up_at', '`follow_up_at` DATETIME DEFAULT NULL');
CALL mj_safe_add_column('mj_seller_leads', 'last_activity_at', '`last_activity_at` DATETIME DEFAULT NULL');
CALL mj_safe_add_column('mj_seller_leads', 'converted_at', '`converted_at` DATETIME DEFAULT NULL');
CALL mj_safe_add_column('mj_seller_leads', 'closed_at', '`closed_at` DATETIME DEFAULT NULL');
CALL mj_safe_add_column('mj_seller_leads', 'ip_address', '`ip_address` VARCHAR(80) DEFAULT NULL');
CALL mj_safe_add_column('mj_seller_leads', 'user_agent', '`user_agent` VARCHAR(255) DEFAULT NULL');
CALL mj_safe_add_column('mj_seller_leads', 'created_at', '`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP');
CALL mj_safe_add_column('mj_seller_leads', 'updated_at', '`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');


CREATE TABLE IF NOT EXISTS `mj_seller_lead_notes` (
  `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  `lead_id` BIGINT UNSIGNED NOT NULL,
  `user_id` BIGINT UNSIGNED DEFAULT NULL,
  `note` TEXT NOT NULL,
  `status` VARCHAR(40) DEFAULT NULL,
  `follow_up_date` DATE DEFAULT NULL,
  `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_lead_id` (`lead_id`),
  KEY `idx_user_id` (`user_id`),
  KEY `idx_created` (`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS `mj_seller_lead_activities` (
  `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  `lead_id` BIGINT UNSIGNED NOT NULL,
  `user_id` BIGINT UNSIGNED DEFAULT NULL,
  `activity_type` VARCHAR(60) NOT NULL DEFAULT 'note',
  `activity_title` VARCHAR(255) DEFAULT NULL,
  `activity_note` TEXT DEFAULT NULL,
  `meta_json` LONGTEXT DEFAULT NULL,
  `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_lead_id` (`lead_id`),
  KEY `idx_user_id` (`user_id`),
  KEY `idx_type` (`activity_type`),
  KEY `idx_created` (`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS `mj_seller_lead_followups` (
  `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  `lead_id` BIGINT UNSIGNED NOT NULL,
  `seller_user_id` BIGINT UNSIGNED DEFAULT NULL,
  `follow_up_at` DATETIME DEFAULT NULL,
  `follow_up_date` DATE DEFAULT NULL,
  `status` VARCHAR(40) NOT NULL DEFAULT 'pending',
  `note` TEXT DEFAULT NULL,
  `completed_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`),
  KEY `idx_lead_id` (`lead_id`),
  KEY `idx_seller_status` (`seller_user_id`,`status`),
  KEY `idx_followup_at` (`follow_up_at`),
  KEY `idx_followup_date` (`follow_up_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS `mj_crm_leads` (
  `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  `lead_no` VARCHAR(60) DEFAULT NULL,
  `user_id` BIGINT UNSIGNED DEFAULT NULL,
  `assigned_to` BIGINT UNSIGNED DEFAULT NULL,
  `source_type` VARCHAR(40) DEFAULT NULL,
  `source_id` BIGINT UNSIGNED DEFAULT NULL,
  `title` VARCHAR(255) DEFAULT NULL,
  `customer_name` VARCHAR(190) DEFAULT NULL,
  `customer_email` VARCHAR(190) DEFAULT NULL,
  `customer_phone` VARCHAR(80) DEFAULT NULL,
  `country` VARCHAR(120) DEFAULT NULL,
  `state` VARCHAR(120) DEFAULT NULL,
  `city` VARCHAR(120) DEFAULT NULL,
  `message` TEXT DEFAULT NULL,
  `status` VARCHAR(40) NOT NULL DEFAULT 'new',
  `lead_tag` VARCHAR(40) DEFAULT NULL,
  `priority` VARCHAR(40) NOT NULL DEFAULT 'normal',
  `follow_up_at` DATETIME DEFAULT NULL,
  `last_activity_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`),
  KEY `idx_status_created` (`status`,`created_at`),
  KEY `idx_assigned_status` (`assigned_to`,`status`),
  KEY `idx_email` (`customer_email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Keep existing non-prefixed seller leads visible in prefixed compatibility table after install/update.
INSERT INTO `mj_seller_leads` (`id`,`seller_user_id`,`user_id`,`source_type`,`source_id`,`item_type`,`item_id`,`title`,`subject`,`customer_name`,`customer_email`,`customer_phone`,`customer_country`,`message`,`status`,`priority`,`is_read`,`follow_up_at`,`last_activity_at`,`ip_address`,`user_agent`,`created_at`,`updated_at`)
SELECT sl.`id`, sl.`seller_user_id`, sl.`seller_user_id`, sl.`source_type`, sl.`source_id`, sl.`source_type`, sl.`source_id`, sl.`source_title`, sl.`source_title`, sl.`customer_name`, sl.`customer_email`, sl.`customer_phone`, sl.`country`, sl.`customer_message`, sl.`status`, sl.`priority`, sl.`is_read`, sl.`followup_at`, sl.`last_activity_at`, sl.`ip_address`, sl.`user_agent`, sl.`created_at`, COALESCE(sl.`updated_at`, sl.`created_at`)
FROM `seller_leads` sl
WHERE NOT EXISTS (SELECT 1 FROM `mj_seller_leads` ml WHERE ml.`id` = sl.`id`);

INSERT INTO `seller_leads` (`id`,`seller_user_id`,`source_type`,`source_id`,`source_title`,`country`,`customer_name`,`customer_email`,`customer_phone`,`customer_message`,`status`,`priority`,`followup_at`,`last_activity_at`,`is_read`,`ip_address`,`user_agent`,`created_at`,`updated_at`)
SELECT ml.`id`, COALESCE(ml.`seller_user_id`, ml.`user_id`, 0), COALESCE(ml.`source_type`, ml.`item_type`, 'general'), COALESCE(ml.`source_id`, ml.`item_id`, ml.`listing_id`, ml.`company_id`, ml.`deal_id`), COALESCE(ml.`title`, ml.`subject`), ml.`customer_country`, ml.`customer_name`, ml.`customer_email`, ml.`customer_phone`, ml.`message`, ml.`status`, ml.`priority`, ml.`follow_up_at`, ml.`last_activity_at`, ml.`is_read`, ml.`ip_address`, ml.`user_agent`, ml.`created_at`, ml.`updated_at`
FROM `mj_seller_leads` ml
WHERE NOT EXISTS (SELECT 1 FROM `seller_leads` sl WHERE sl.`id` = ml.`id`);

INSERT INTO `mj_schema_migrations` (`version`, `migration_file`, `status`, `started_at`, `completed_at`)
VALUES ('3.0.4.2.4', 'update_to_v3.0.4.2.4.sql', 'completed', NOW(), NOW())
ON DUPLICATE KEY UPDATE `status`='completed', `completed_at`=NOW(), `error_message`=NULL;


-- ------------------------------------------------------------
-- v3.0.4.2.5 Sample Data Schema Safety Repair
-- This allows sample data upload on existing working websites where
-- some v3.0.4.x columns/tables were not present yet.
-- Safe to run multiple times. No data deleted.
-- ------------------------------------------------------------
CREATE TABLE IF NOT EXISTS mj_subscriptions (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT NULL,
  company_id BIGINT NULL,
  plan_name VARCHAR(120) NULL,
  status VARCHAR(40) DEFAULT 'active',
  starts_at DATETIME NULL,
  ends_at DATETIME NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CALL mj_safe_add_column('mj_companies', 'approved_at', 'approved_at DATETIME NULL');
CALL mj_safe_add_column('mj_companies', 'approved_by', 'approved_by BIGINT NULL');
CALL mj_safe_add_column('mj_companies', 'submitted_at', 'submitted_at DATETIME NULL');
CALL mj_safe_add_column('mj_companies', 'admin_note', 'admin_note TEXT NULL');
CALL mj_safe_add_column('mj_companies', 'rejection_reason', 'rejection_reason TEXT NULL');
CALL mj_safe_add_column('mj_companies', 'is_sponsored', 'is_sponsored TINYINT(1) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_companies', 'sponsored_until', 'sponsored_until DATETIME NULL');

CALL mj_safe_add_column('mj_deals', 'approved_at', 'approved_at DATETIME NULL');
CALL mj_safe_add_column('mj_deals', 'approved_by', 'approved_by BIGINT NULL');
CALL mj_safe_add_column('mj_deals', 'featured_until', 'featured_until DATETIME NULL');
CALL mj_safe_add_column('mj_deals', 'sponsored_until', 'sponsored_until DATETIME NULL');
CALL mj_safe_add_column('mj_deals', 'enquiry_count', 'enquiry_count INT NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_deals', 'view_count', 'view_count INT NOT NULL DEFAULT 0');

CALL mj_safe_add_column('mj_listings', 'approved_at', 'approved_at DATETIME NULL');
CALL mj_safe_add_column('mj_listings', 'approved_by', 'approved_by BIGINT NULL');
CALL mj_safe_add_column('mj_listings', 'company_id', 'company_id BIGINT NULL');
CALL mj_safe_add_column('mj_listings', 'branch_id', 'branch_id BIGINT NULL');
CALL mj_safe_add_column('mj_listings', 'published_at', 'published_at DATETIME NULL');
CALL mj_safe_add_column('mj_listings', 'expires_at', 'expires_at DATETIME NULL');

CALL mj_safe_add_column('mj_subscriptions', 'user_id', 'user_id BIGINT NULL');
CALL mj_safe_add_column('mj_subscriptions', 'company_id', 'company_id BIGINT NULL');
CALL mj_safe_add_column('mj_subscriptions', 'plan_name', 'plan_name VARCHAR(120) NULL');
CALL mj_safe_add_column('mj_subscriptions', 'status', 'status VARCHAR(40) DEFAULT ''active''');
CALL mj_safe_add_column('mj_subscriptions', 'starts_at', 'starts_at DATETIME NULL');
CALL mj_safe_add_column('mj_subscriptions', 'ends_at', 'ends_at DATETIME NULL');
CALL mj_safe_add_column('mj_subscriptions', 'created_at', 'created_at DATETIME DEFAULT CURRENT_TIMESTAMP');

CREATE TABLE IF NOT EXISTS seller_leads (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  seller_user_id BIGINT UNSIGNED NOT NULL DEFAULT 0,
  source_type VARCHAR(30) NOT NULL DEFAULT 'listing',
  source_id BIGINT UNSIGNED DEFAULT NULL,
  source_title VARCHAR(255) DEFAULT NULL,
  country VARCHAR(100) DEFAULT NULL,
  customer_name VARCHAR(160) DEFAULT NULL,
  customer_email VARCHAR(190) DEFAULT NULL,
  customer_phone VARCHAR(80) DEFAULT NULL,
  customer_message TEXT DEFAULT NULL,
  status VARCHAR(40) NOT NULL DEFAULT 'new',
  priority VARCHAR(20) NOT NULL DEFAULT 'normal',
  followup_at DATETIME DEFAULT NULL,
  last_activity_at DATETIME DEFAULT NULL,
  is_read TINYINT(1) NOT NULL DEFAULT 0,
  ip_address VARCHAR(80) DEFAULT NULL,
  user_agent VARCHAR(255) DEFAULT NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  KEY idx_seller_status_created (seller_user_id,status,created_at),
  KEY idx_source (source_type,source_id),
  KEY idx_customer_email (customer_email),
  KEY idx_followup (seller_user_id,followup_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS seller_lead_activities (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  lead_id BIGINT NOT NULL,
  user_id BIGINT NULL,
  user_type VARCHAR(20) DEFAULT 'seller',
  activity_type VARCHAR(40) NOT NULL DEFAULT 'note',
  note TEXT NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  KEY idx_lead_created (lead_id,created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_seller_leads LIKE seller_leads;
CREATE TABLE IF NOT EXISTS mj_seller_lead_activities LIKE seller_lead_activities;
CREATE TABLE IF NOT EXISTS mj_seller_lead_notes (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  lead_id BIGINT NOT NULL,
  user_id BIGINT NULL,
  note TEXT NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  KEY idx_lead_created (lead_id,created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_seller_lead_followups (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  lead_id BIGINT NOT NULL,
  user_id BIGINT NULL,
  followup_at DATETIME NULL,
  status VARCHAR(40) DEFAULT 'pending',
  note TEXT NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  KEY idx_lead_followup (lead_id,followup_at),
  KEY idx_status_followup (status,followup_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;


-- MJ Marketplace v3.0.4.2.6 Leads Layout Fix marker
CREATE TABLE IF NOT EXISTS mj_schema_migrations (
  id INT AUTO_INCREMENT PRIMARY KEY,
  version VARCHAR(50) NOT NULL,
  migration_file VARCHAR(255) NOT NULL,
  status VARCHAR(30) NOT NULL DEFAULT 'completed',
  started_at DATETIME NULL,
  completed_at DATETIME NULL,
  error_message TEXT NULL,
  UNIQUE KEY uniq_version_file (version, migration_file)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO mj_schema_migrations (version, migration_file, status, started_at, completed_at)
VALUES ('3.0.4.2.6', 'migration_v3.0.4.2.6_leads_layout_fix.sql', 'completed', NOW(), NOW())
ON DUPLICATE KEY UPDATE status='completed', completed_at=NOW(), error_message=NULL;


-- v3.0.4.2.7 form security and fast location
-- MJ Marketplace v3.0.4.2.7 Form Security + Fast Location UX
-- Safe for existing websites. Does not delete data.
CREATE TABLE IF NOT EXISTS mj_form_tokens (
  id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  session_id VARCHAR(128) NOT NULL,
  token_hash CHAR(64) NOT NULL,
  form_key VARCHAR(160) NULL,
  expires_at DATETIME NOT NULL,
  used_at DATETIME NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_session_token(session_id, token_hash),
  INDEX idx_expires(expires_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_form_duplicate_guard (
  id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  guard_hash CHAR(64) NOT NULL UNIQUE,
  form_key VARCHAR(160) NULL,
  user_type VARCHAR(30) NULL,
  user_id BIGINT UNSIGNED NULL,
  ip_address VARCHAR(80) NULL,
  user_agent VARCHAR(255) NULL,
  first_seen_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  last_seen_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  hit_count INT NOT NULL DEFAULT 1,
  INDEX idx_form_user(form_key,user_type,user_id),
  INDEX idx_last(last_seen_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_form_rate_limits (
  id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  rate_key CHAR(64) NOT NULL UNIQUE,
  form_key VARCHAR(160) NULL,
  ip_address VARCHAR(80) NULL,
  user_id BIGINT UNSIGNED NULL,
  request_count INT NOT NULL DEFAULT 1,
  window_start DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  last_request_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_window(window_start),
  INDEX idx_ip(ip_address)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_form_submission_log (
  id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  form_key VARCHAR(160) NULL,
  user_type VARCHAR(30) NULL,
  user_id BIGINT UNSIGNED NULL,
  ip_address VARCHAR(80) NULL,
  status VARCHAR(40) NOT NULL DEFAULT 'received',
  message VARCHAR(255) NULL,
  guard_hash CHAR(64) NULL,
  request_uri VARCHAR(500) NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_form_created(form_key,created_at),
  INDEX idx_status(status),
  INDEX idx_user(user_type,user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_security_events (
  id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  event_type VARCHAR(80) NOT NULL,
  severity VARCHAR(30) NOT NULL DEFAULT 'info',
  message VARCHAR(255) NULL,
  user_type VARCHAR(30) NULL,
  user_id BIGINT UNSIGNED NULL,
  ip_address VARCHAR(80) NULL,
  user_agent VARCHAR(255) NULL,
  request_uri VARCHAR(500) NULL,
  meta_json LONGTEXT NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_type_created(event_type,created_at),
  INDEX idx_severity(severity)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS mj_location_cache (
  id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  cache_key CHAR(64) NOT NULL UNIQUE,
  cache_type VARCHAR(40) NOT NULL,
  query_text VARCHAR(180) NULL,
  country_id INT NULL,
  state_id BIGINT NULL,
  result_json LONGTEXT NULL,
  hit_count INT NOT NULL DEFAULT 0,
  expires_at DATETIME NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT NULL,
  INDEX idx_type_query(cache_type, query_text),
  INDEX idx_country_state(country_id,state_id),
  INDEX idx_expires(expires_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CALL mj_safe_add_index('mj_countries', 'idx_mj427_country_search', 'ADD INDEX idx_mj427_country_search (is_active, name, iso2)');
CALL mj_safe_add_index('mj_states', 'idx_mj427_state_search', 'ADD INDEX idx_mj427_state_search (country_id, is_active, name)');
CALL mj_safe_add_index('mj_cities', 'idx_mj427_city_search', 'ADD INDEX idx_mj427_city_search (country_id, state_id, is_active, name)');
INSERT INTO mj_schema_migrations(version,migration_file,status,started_at,completed_at)
VALUES('3.0.4.2.7','migration_v3.0.4.2.7_form_security_fast_location.sql','completed',NOW(),NOW())
ON DUPLICATE KEY UPDATE status='completed', completed_at=NOW();

-- ===== v3.0.4.2.8 Popup + Speed + Auto Repair + Debug Mode =====
-- MJ Marketplace v3.0.4.2.8 Popup + Speed + Auto Repair + Debug Mode
-- Safe for existing websites. Does not delete data.

CREATE TABLE IF NOT EXISTS mj_error_logs (
  id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  error_hash CHAR(64) NULL,
  error_type VARCHAR(80) NOT NULL DEFAULT 'runtime',
  severity VARCHAR(30) NOT NULL DEFAULT 'error',
  message LONGTEXT NULL,
  file_path VARCHAR(500) NULL,
  line_number INT NULL,
  sql_text LONGTEXT NULL,
  request_uri VARCHAR(500) NULL,
  user_type VARCHAR(30) NULL,
  user_id BIGINT UNSIGNED NULL,
  ip_address VARCHAR(80) NULL,
  user_agent VARCHAR(255) NULL,
  stack_trace LONGTEXT NULL,
  is_resolved TINYINT(1) NOT NULL DEFAULT 0,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_type_created(error_type, created_at),
  INDEX idx_severity(severity),
  INDEX idx_hash(error_hash),
  INDEX idx_resolved(is_resolved)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_auto_repair_logs (
  id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  repair_key VARCHAR(160) NULL,
  repair_type VARCHAR(80) NOT NULL,
  status VARCHAR(40) NOT NULL DEFAULT 'completed',
  message VARCHAR(255) NULL,
  sql_text LONGTEXT NULL,
  triggered_by VARCHAR(80) NULL,
  request_uri VARCHAR(500) NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_repair_created(repair_type, created_at),
  INDEX idx_status(status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_page_cache (
  cache_key VARCHAR(180) PRIMARY KEY,
  cache_group VARCHAR(80) NOT NULL DEFAULT 'page',
  cache_value LONGTEXT NULL,
  expires_at DATETIME NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NULL,
  INDEX idx_group_expiry(cache_group, expires_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_count_cache (
  cache_key VARCHAR(180) PRIMARY KEY,
  cache_group VARCHAR(80) NOT NULL DEFAULT 'count',
  cache_value BIGINT NOT NULL DEFAULT 0,
  expires_at DATETIME NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NULL,
  INDEX idx_group_expiry(cache_group, expires_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_performance_log (
  id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  event_type VARCHAR(80) NOT NULL,
  duration_ms INT NULL,
  request_uri VARCHAR(500) NULL,
  meta_json LONGTEXT NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_event_created(event_type, created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_schema_migrations (
  id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  version VARCHAR(40) NOT NULL UNIQUE,
  migration_file VARCHAR(255) NULL,
  status VARCHAR(40) NOT NULL DEFAULT 'completed',
  started_at DATETIME NULL,
  completed_at DATETIME NULL,
  error_message TEXT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

INSERT INTO mj_settings(setting_key,setting_value) VALUES
('debug_mode','0'),
('auto_repair_enabled','1'),
('popup_messages_enabled','1'),
('page_speed_mode','1'),
('slow_query_log_ms','1200')
ON DUPLICATE KEY UPDATE setting_value=setting_value;

-- Important compatibility columns used by sample data, leads, review and payment flows.
CALL mj_safe_add_column('mj_companies', 'approved_at', 'approved_at DATETIME NULL');
CALL mj_safe_add_column('mj_companies', 'approved_by', 'approved_by INT NULL');
CALL mj_safe_add_column('mj_companies', 'verification_status', 'verification_status VARCHAR(40) NOT NULL DEFAULT ''unverified''');
CALL mj_safe_add_column('mj_companies', 'admin_note', 'admin_note TEXT NULL');
CALL mj_safe_add_column('mj_companies', 'rejection_reason', 'rejection_reason TEXT NULL');

CALL mj_safe_add_column('mj_listings', 'approved_at', 'approved_at DATETIME NULL');
CALL mj_safe_add_column('mj_listings', 'approved_by', 'approved_by INT NULL');
CALL mj_safe_add_column('mj_listings', 'admin_note', 'admin_note TEXT NULL');
CALL mj_safe_add_column('mj_listings', 'rejection_reason', 'rejection_reason TEXT NULL');

CALL mj_safe_add_column('mj_deals', 'approved_at', 'approved_at DATETIME NULL');
CALL mj_safe_add_column('mj_deals', 'approved_by', 'approved_by INT NULL');
CALL mj_safe_add_column('mj_deals', 'admin_note', 'admin_note TEXT NULL');
CALL mj_safe_add_column('mj_deals', 'rejection_reason', 'rejection_reason TEXT NULL');
CALL mj_safe_add_column('mj_deals', 'seller_user_id', 'seller_user_id INT NULL');

CALL mj_safe_add_column('mj_orders', 'payment_status', 'payment_status VARCHAR(40) DEFAULT ''payment_disabled''');
CALL mj_safe_add_column('mj_orders', 'payment_method', 'payment_method VARCHAR(80) NULL');
CALL mj_safe_add_column('mj_orders', 'payment_reference', 'payment_reference VARCHAR(180) NULL');
CALL mj_safe_add_column('mj_orders', 'payment_proof_path', 'payment_proof_path VARCHAR(500) NULL');
CALL mj_safe_add_column('mj_orders', 'payment_verified_at', 'payment_verified_at DATETIME NULL');
CALL mj_safe_add_column('mj_orders', 'admin_note', 'admin_note TEXT NULL');

CALL mj_safe_add_column('mj_notifications', 'is_read', 'is_read TINYINT(1) NOT NULL DEFAULT 0');
CALL mj_safe_add_column('mj_notifications', 'read_at', 'read_at DATETIME NULL');
CALL mj_safe_add_column('mj_notifications', 'link_url', 'link_url VARCHAR(500) NULL');

CREATE TABLE IF NOT EXISTS mj_seller_leads (
  id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  seller_user_id BIGINT UNSIGNED NULL,
  lead_type VARCHAR(40) NULL,
  source_type VARCHAR(40) NULL,
  source_id BIGINT UNSIGNED NULL,
  item_title VARCHAR(255) NULL,
  customer_name VARCHAR(180) NULL,
  customer_email VARCHAR(180) NULL,
  customer_phone VARCHAR(80) NULL,
  customer_whatsapp VARCHAR(80) NULL,
  subject VARCHAR(255) NULL,
  message TEXT NULL,
  status VARCHAR(40) NOT NULL DEFAULT 'new',
  priority VARCHAR(40) NOT NULL DEFAULT 'normal',
  follow_up_date DATE NULL,
  last_note TEXT NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NULL,
  INDEX idx_seller_status(seller_user_id,status),
  INDEX idx_source(source_type,source_id),
  INDEX idx_created(created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS seller_leads LIKE mj_seller_leads;

CREATE TABLE IF NOT EXISTS mj_seller_lead_notes (
  id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  lead_id BIGINT UNSIGNED NOT NULL,
  user_type VARCHAR(30) NULL,
  user_id BIGINT UNSIGNED NULL,
  note TEXT NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_lead(lead_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_seller_lead_activities (
  id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  lead_id BIGINT UNSIGNED NOT NULL,
  activity_type VARCHAR(80) NULL,
  activity_note TEXT NULL,
  created_by BIGINT UNSIGNED NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_lead(lead_id),
  INDEX idx_type(activity_type)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS seller_lead_activities LIKE mj_seller_lead_activities;

CREATE TABLE IF NOT EXISTS mj_seller_lead_followups (
  id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  lead_id BIGINT UNSIGNED NOT NULL,
  follow_up_date DATETIME NULL,
  status VARCHAR(40) NOT NULL DEFAULT 'pending',
  note TEXT NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_lead_status(lead_id,status),
  INDEX idx_due(follow_up_date,status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mj_crm_leads (
  id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  seller_user_id BIGINT UNSIGNED NULL,
  source_type VARCHAR(40) NULL,
  source_id BIGINT UNSIGNED NULL,
  customer_name VARCHAR(180) NULL,
  customer_email VARCHAR(180) NULL,
  customer_phone VARCHAR(80) NULL,
  message TEXT NULL,
  status VARCHAR(40) NOT NULL DEFAULT 'new',
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_seller_status(seller_user_id,status),
  INDEX idx_created(created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- Speed indexes. MariaDB supports IF NOT EXISTS on newer versions; installer will skip duplicate index errors safely.
CALL mj_safe_add_index('mj_listings', 'idx_mj428_listing_speed', 'ADD INDEX idx_mj428_listing_speed (status,country_id,city_id,category_id,user_id,created_at)');
CALL mj_safe_add_index('mj_companies', 'idx_mj428_company_speed', 'ADD INDEX idx_mj428_company_speed (status,country_id,city_id,user_id,created_at)');
CALL mj_safe_add_index('mj_deals', 'idx_mj428_deal_speed', 'ADD INDEX idx_mj428_deal_speed (status,country_id,city_id,category_id,seller_user_id,created_at)');
CALL mj_safe_add_index('mj_notifications', 'idx_mj428_notify_speed', 'ADD INDEX idx_mj428_notify_speed (user_id,is_read,created_at)');
CALL mj_safe_add_index('seller_leads', 'idx_mj428_leads_speed', 'ADD INDEX idx_mj428_leads_speed (seller_user_id,status,source_type,created_at)');
CALL mj_safe_add_index('mj_seller_leads', 'idx_mj428_mjleads_speed', 'ADD INDEX idx_mj428_mjleads_speed (seller_user_id,status,source_type,created_at)');
CALL mj_safe_add_index('mj_payments', 'idx_mj428_payment_speed', 'ADD INDEX idx_mj428_payment_speed (user_id,status,created_at)');
CALL mj_safe_add_index('mj_countries', 'idx_mj428_country_lookup', 'ADD INDEX idx_mj428_country_lookup (is_active,name,iso2)');
CALL mj_safe_add_index('mj_states', 'idx_mj428_state_lookup', 'ADD INDEX idx_mj428_state_lookup (country_id,is_active,name)');
CALL mj_safe_add_index('mj_cities', 'idx_mj428_city_lookup', 'ADD INDEX idx_mj428_city_lookup (country_id,state_id,is_active,name)');

INSERT INTO mj_schema_migrations(version,migration_file,status,started_at,completed_at)
VALUES('3.0.4.2.8','migration_v3.0.4.2.8_popup_speed_auto_repair_debug.sql','completed',NOW(),NOW())
ON DUPLICATE KEY UPDATE status='completed', completed_at=NOW();

