Agent-Logs-Url: https://github.com/GameServerPanel/GSP/sessions/5dbd58e1-7aa0-41e2-8dd3-c56b69ede05e Co-authored-by: iaretechnician <2749183+iaretechnician@users.noreply.github.com>
260 lines
12 KiB
SQL
260 lines
12 KiB
SQL
-- ============================================================
|
||
-- GSP Billing Status Simplification Migration
|
||
-- Simplifies server billing lifecycle to: Active | Invoiced | Expired
|
||
--
|
||
-- Run manually ONCE on an existing installation.
|
||
-- Safe to re-run: every ALTER uses IF NOT EXISTS / PREPARE guards.
|
||
-- Table prefix: gsp_ (matches modules/billing/includes/config.inc.php)
|
||
--
|
||
-- BACK UP YOUR DATABASE BEFORE RUNNING THIS SCRIPT.
|
||
-- ============================================================
|
||
|
||
SET @dbname = DATABASE();
|
||
|
||
-- ============================================================
|
||
-- SECTION 1: Fix gsp_server_homes.server_expiration_date
|
||
-- Convert from VARCHAR(21) with 'X' default to DATETIME NULL
|
||
-- ============================================================
|
||
|
||
-- Clear placeholder 'X' and empty-string values so the column
|
||
-- can be safely converted to DATETIME.
|
||
UPDATE `gsp_server_homes`
|
||
SET `server_expiration_date` = NULL
|
||
WHERE `server_expiration_date` IN ('X', '', '0', '0000-00-00 00:00:00')
|
||
OR `server_expiration_date` IS NULL;
|
||
|
||
-- Convert to DATETIME only when it is still stored as VARCHAR.
|
||
SET @col_type = '';
|
||
SELECT DATA_TYPE INTO @col_type
|
||
FROM information_schema.COLUMNS
|
||
WHERE TABLE_SCHEMA = @dbname
|
||
AND TABLE_NAME = 'gsp_server_homes'
|
||
AND COLUMN_NAME = 'server_expiration_date';
|
||
|
||
SET @sql = IF(
|
||
@col_type = 'varchar',
|
||
'ALTER TABLE `gsp_server_homes` MODIFY COLUMN `server_expiration_date` DATETIME NULL DEFAULT NULL',
|
||
'SELECT "server_expiration_date already DATETIME – skipping" AS _msg'
|
||
);
|
||
PREPARE _stmt FROM @sql; EXECUTE _stmt; DEALLOCATE PREPARE _stmt;
|
||
|
||
-- ============================================================
|
||
-- SECTION 2: Add new billing lifecycle columns to gsp_server_homes
|
||
-- ============================================================
|
||
|
||
-- billing_status
|
||
SET @col_exists = 0;
|
||
SELECT COUNT(*) INTO @col_exists
|
||
FROM information_schema.COLUMNS
|
||
WHERE TABLE_SCHEMA = @dbname AND TABLE_NAME = 'gsp_server_homes' AND COLUMN_NAME = 'billing_status';
|
||
SET @sql = IF(
|
||
@col_exists = 0,
|
||
'ALTER TABLE `gsp_server_homes` ADD COLUMN `billing_status` ENUM(\'Active\',\'Invoiced\',\'Expired\') NOT NULL DEFAULT \'Active\' AFTER `server_expiration_date`',
|
||
'SELECT "billing_status already exists" AS _msg'
|
||
);
|
||
PREPARE _stmt FROM @sql; EXECUTE _stmt; DEALLOCATE PREPARE _stmt;
|
||
|
||
-- next_invoice_date
|
||
SET @col_exists = 0;
|
||
SELECT COUNT(*) INTO @col_exists
|
||
FROM information_schema.COLUMNS
|
||
WHERE TABLE_SCHEMA = @dbname AND TABLE_NAME = 'gsp_server_homes' AND COLUMN_NAME = 'next_invoice_date';
|
||
SET @sql = IF(
|
||
@col_exists = 0,
|
||
'ALTER TABLE `gsp_server_homes` ADD COLUMN `next_invoice_date` DATETIME NULL DEFAULT NULL AFTER `billing_status`',
|
||
'SELECT "next_invoice_date already exists" AS _msg'
|
||
);
|
||
PREPARE _stmt FROM @sql; EXECUTE _stmt; DEALLOCATE PREPARE _stmt;
|
||
|
||
-- last_invoice_id
|
||
SET @col_exists = 0;
|
||
SELECT COUNT(*) INTO @col_exists
|
||
FROM information_schema.COLUMNS
|
||
WHERE TABLE_SCHEMA = @dbname AND TABLE_NAME = 'gsp_server_homes' AND COLUMN_NAME = 'last_invoice_id';
|
||
SET @sql = IF(
|
||
@col_exists = 0,
|
||
'ALTER TABLE `gsp_server_homes` ADD COLUMN `last_invoice_id` INT NULL DEFAULT NULL AFTER `next_invoice_date`',
|
||
'SELECT "last_invoice_id already exists" AS _msg'
|
||
);
|
||
PREPARE _stmt FROM @sql; EXECUTE _stmt; DEALLOCATE PREPARE _stmt;
|
||
|
||
-- billing_enabled
|
||
SET @col_exists = 0;
|
||
SELECT COUNT(*) INTO @col_exists
|
||
FROM information_schema.COLUMNS
|
||
WHERE TABLE_SCHEMA = @dbname AND TABLE_NAME = 'gsp_server_homes' AND COLUMN_NAME = 'billing_enabled';
|
||
SET @sql = IF(
|
||
@col_exists = 0,
|
||
'ALTER TABLE `gsp_server_homes` ADD COLUMN `billing_enabled` TINYINT(1) NOT NULL DEFAULT 1 AFTER `last_invoice_id`',
|
||
'SELECT "billing_enabled already exists" AS _msg'
|
||
);
|
||
PREPARE _stmt FROM @sql; EXECUTE _stmt; DEALLOCATE PREPARE _stmt;
|
||
|
||
-- ============================================================
|
||
-- SECTION 3: Create gsp_invoices (post-purchase renewal invoices)
|
||
-- Distinct from gsp_billing_invoices (pre-purchase cart)
|
||
-- ============================================================
|
||
|
||
CREATE TABLE IF NOT EXISTS `gsp_invoices` (
|
||
`invoice_id` INT NOT NULL AUTO_INCREMENT,
|
||
`home_id` INT NOT NULL,
|
||
`user_id` INT NOT NULL,
|
||
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
`due_date` DATETIME NOT NULL,
|
||
`paid_at` DATETIME NULL DEFAULT NULL,
|
||
`billing_status` ENUM('Invoiced','Active','Expired')
|
||
NOT NULL DEFAULT 'Invoiced',
|
||
`rate_type` ENUM('daily','monthly','yearly')
|
||
NOT NULL DEFAULT 'monthly',
|
||
`price_per_player` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
|
||
`player_slots` INT NOT NULL DEFAULT 0,
|
||
`quantity` INT NOT NULL DEFAULT 1,
|
||
`subtotal` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
|
||
`total_due` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
|
||
`payment_method` VARCHAR(64) NOT NULL DEFAULT 'PayPal',
|
||
`payment_id` VARCHAR(255) NULL DEFAULT NULL,
|
||
`notes` TEXT NULL,
|
||
PRIMARY KEY (`invoice_id`),
|
||
KEY `idx_home_id` (`home_id`),
|
||
KEY `idx_user_id` (`user_id`),
|
||
KEY `idx_billing_status`(`billing_status`),
|
||
KEY `idx_due_date` (`due_date`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||
|
||
-- ============================================================
|
||
-- SECTION 4: Create gsp_billing_config (per-game or global rates)
|
||
-- ============================================================
|
||
|
||
CREATE TABLE IF NOT EXISTS `gsp_billing_config` (
|
||
`config_id` INT NOT NULL AUTO_INCREMENT,
|
||
`game_key` VARCHAR(128) NULL DEFAULT NULL COMMENT 'NULL = global default',
|
||
`rate_type` ENUM('daily','monthly','yearly')
|
||
NOT NULL DEFAULT 'monthly',
|
||
`price_per_player` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
|
||
`grace_days` INT NOT NULL DEFAULT 0,
|
||
`delete_after_expired_days`INT NOT NULL DEFAULT 7,
|
||
`enabled` TINYINT(1) NOT NULL DEFAULT 1,
|
||
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
`updated_at` DATETIME NULL ON UPDATE CURRENT_TIMESTAMP,
|
||
PRIMARY KEY (`config_id`),
|
||
KEY `idx_game_key` (`game_key`),
|
||
KEY `idx_enabled` (`enabled`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||
|
||
-- Insert global default config if none exists
|
||
INSERT INTO `gsp_billing_config`
|
||
(`game_key`, `rate_type`, `price_per_player`, `grace_days`, `delete_after_expired_days`, `enabled`)
|
||
SELECT NULL, 'monthly', 0.00, 0, 7, 1
|
||
WHERE NOT EXISTS (
|
||
SELECT 1 FROM `gsp_billing_config` WHERE `game_key` IS NULL LIMIT 1
|
||
);
|
||
|
||
-- ============================================================
|
||
-- SECTION 5: Populate server_homes.billing_status from existing
|
||
-- gsp_billing_orders data
|
||
-- Priority: Expired > Invoiced > Active
|
||
-- ============================================================
|
||
|
||
-- Active: paid, installed, active, running, enabled, online
|
||
UPDATE `gsp_server_homes` sh
|
||
INNER JOIN `gsp_billing_orders` bo
|
||
ON bo.home_id = sh.home_id
|
||
AND CAST(bo.home_id AS UNSIGNED) > 0
|
||
SET sh.`billing_status` = 'Active',
|
||
sh.`server_expiration_date` = bo.`end_date`,
|
||
sh.`next_invoice_date` = bo.`end_date`
|
||
WHERE bo.`status` IN ('paid', 'installed', 'active', 'running', 'enabled', 'online');
|
||
|
||
-- Invoiced: renew, unpaid, pending, overdue, invoice, invoiced, in-cart
|
||
UPDATE `gsp_server_homes` sh
|
||
INNER JOIN `gsp_billing_orders` bo
|
||
ON bo.home_id = sh.home_id
|
||
AND CAST(bo.home_id AS UNSIGNED) > 0
|
||
SET sh.`billing_status` = 'Invoiced',
|
||
sh.`server_expiration_date` = bo.`end_date`,
|
||
sh.`next_invoice_date` = bo.`end_date`
|
||
WHERE bo.`status` IN ('renew', 'unpaid', 'pending', 'overdue', 'invoice', 'invoiced', 'in-cart');
|
||
|
||
-- Expired: expired, cancelled, terminated, suspended, deleted
|
||
UPDATE `gsp_server_homes` sh
|
||
INNER JOIN `gsp_billing_orders` bo
|
||
ON bo.home_id = sh.home_id
|
||
AND CAST(bo.home_id AS UNSIGNED) > 0
|
||
SET sh.`billing_status` = 'Expired',
|
||
sh.`server_expiration_date` = bo.`end_date`
|
||
WHERE bo.`status` IN ('expired', 'cancelled', 'terminated', 'suspended', 'deleted');
|
||
|
||
-- Backfill server_expiration_date from billing_orders where still NULL
|
||
UPDATE `gsp_server_homes` sh
|
||
INNER JOIN `gsp_billing_orders` bo
|
||
ON bo.home_id = sh.home_id
|
||
AND CAST(bo.home_id AS UNSIGNED) > 0
|
||
SET sh.`server_expiration_date` = bo.`end_date`
|
||
WHERE sh.`server_expiration_date` IS NULL
|
||
AND bo.`end_date` IS NOT NULL;
|
||
|
||
-- ============================================================
|
||
-- SECTION 6: Normalize gsp_billing_orders.status to new values
|
||
-- ============================================================
|
||
|
||
-- Active (was: paid, installed, active, running, enabled, online)
|
||
UPDATE `gsp_billing_orders`
|
||
SET `status` = 'Active'
|
||
WHERE `status` IN ('paid', 'installed', 'active', 'running', 'enabled', 'online');
|
||
|
||
-- Invoiced (was: renew, unpaid, pending, overdue, invoice, invoiced, in-cart)
|
||
UPDATE `gsp_billing_orders`
|
||
SET `status` = 'Invoiced'
|
||
WHERE `status` IN ('renew', 'unpaid', 'pending', 'overdue', 'invoice', 'invoiced', 'in-cart');
|
||
|
||
-- Expired (was: expired, cancelled, terminated, suspended, deleted)
|
||
UPDATE `gsp_billing_orders`
|
||
SET `status` = 'Expired'
|
||
WHERE `status` IN ('expired', 'cancelled', 'terminated', 'suspended', 'deleted');
|
||
|
||
-- ============================================================
|
||
-- SECTION 7: Add indexes to gsp_server_homes for billing queries
|
||
-- ============================================================
|
||
|
||
SET @idx = 0;
|
||
SELECT COUNT(*) INTO @idx FROM information_schema.STATISTICS
|
||
WHERE TABLE_SCHEMA = @dbname AND TABLE_NAME = 'gsp_server_homes' AND INDEX_NAME = 'idx_billing_status';
|
||
SET @sql = IF(@idx = 0,
|
||
'ALTER TABLE `gsp_server_homes` ADD INDEX `idx_billing_status` (`billing_status`)',
|
||
'SELECT "idx_billing_status exists" AS _msg');
|
||
PREPARE _stmt FROM @sql; EXECUTE _stmt; DEALLOCATE PREPARE _stmt;
|
||
|
||
SET @idx = 0;
|
||
SELECT COUNT(*) INTO @idx FROM information_schema.STATISTICS
|
||
WHERE TABLE_SCHEMA = @dbname AND TABLE_NAME = 'gsp_server_homes' AND INDEX_NAME = 'idx_server_expiration_date';
|
||
SET @sql = IF(@idx = 0,
|
||
'ALTER TABLE `gsp_server_homes` ADD INDEX `idx_server_expiration_date` (`server_expiration_date`)',
|
||
'SELECT "idx_server_expiration_date exists" AS _msg');
|
||
PREPARE _stmt FROM @sql; EXECUTE _stmt; DEALLOCATE PREPARE _stmt;
|
||
|
||
SET @idx = 0;
|
||
SELECT COUNT(*) INTO @idx FROM information_schema.STATISTICS
|
||
WHERE TABLE_SCHEMA = @dbname AND TABLE_NAME = 'gsp_server_homes' AND INDEX_NAME = 'idx_next_invoice_date';
|
||
SET @sql = IF(@idx = 0,
|
||
'ALTER TABLE `gsp_server_homes` ADD INDEX `idx_next_invoice_date` (`next_invoice_date`)',
|
||
'SELECT "idx_next_invoice_date exists" AS _msg');
|
||
PREPARE _stmt FROM @sql; EXECUTE _stmt; DEALLOCATE PREPARE _stmt;
|
||
|
||
SET @idx = 0;
|
||
SELECT COUNT(*) INTO @idx FROM information_schema.STATISTICS
|
||
WHERE TABLE_SCHEMA = @dbname AND TABLE_NAME = 'gsp_server_homes' AND INDEX_NAME = 'idx_billing_enabled';
|
||
SET @sql = IF(@idx = 0,
|
||
'ALTER TABLE `gsp_server_homes` ADD INDEX `idx_billing_enabled` (`billing_enabled`)',
|
||
'SELECT "idx_billing_enabled exists" AS _msg');
|
||
PREPARE _stmt FROM @sql; EXECUTE _stmt; DEALLOCATE PREPARE _stmt;
|
||
|
||
-- ============================================================
|
||
-- DONE
|
||
-- ============================================================
|
||
|
||
SELECT CONCAT(
|
||
'Migration complete. ',
|
||
'gsp_server_homes now has billing_status/next_invoice_date/last_invoice_id/billing_enabled. ',
|
||
'gsp_invoices and gsp_billing_config tables created. ',
|
||
'gsp_billing_orders.status normalized to Active/Invoiced/Expired.'
|
||
) AS Migration_Result;
|