-- ============================================================================
--  2026_sprint30k_phase4_runtime_pass2.sql — فاز ۴ / پاسِ دومِ رفعِ runtime
--  ADDITIVE فقط / idempotent / بدونِ DELIMITER / بدونِ stored procedure /
--  سازگار با cPanel و MariaDB/MySQL / غیرِمخرب (هیچ داده‌ای حذف/بازنویسی نمی‌شود).
--  پیش‌نیاز: پس از ...30i و 30j اجرا شود. اجرای چندباره بی‌خطر است.
--  هیچ UNIQUE جدیدی اضافه نمی‌شود؛ گزارش‌های تشخیصی/remediation در انتها آمده‌اند.
-- ============================================================================
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;


-- ─────────────── cw_aftercare_deliveries (D2) ───────────────
SET @c:=(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='cw_aftercare_deliveries' AND COLUMN_NAME='claimed_at');
SET @s:=IF(@c=0,"ALTER TABLE `cw_aftercare_deliveries` ADD COLUMN `claimed_at` DATETIME DEFAULT NULL",'SELECT 1'); PREPARE st FROM @s; EXECUTE st; DEALLOCATE PREPARE st;

SET @c:=(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='cw_aftercare_deliveries' AND COLUMN_NAME='claim_token');
SET @s:=IF(@c=0,"ALTER TABLE `cw_aftercare_deliveries` ADD COLUMN `claim_token` VARCHAR(32) DEFAULT NULL",'SELECT 1'); PREPARE st FROM @s; EXECUTE st; DEALLOCATE PREPARE st;

SET @c:=(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='cw_aftercare_deliveries' AND COLUMN_NAME='claim_expires_at');
SET @s:=IF(@c=0,"ALTER TABLE `cw_aftercare_deliveries` ADD COLUMN `claim_expires_at` DATETIME DEFAULT NULL",'SELECT 1'); PREPARE st FROM @s; EXECUTE st; DEALLOCATE PREPARE st;


-- ─────────────── nurses (D5) ───────────────
SET @c:=(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='nurses' AND COLUMN_NAME='is_laser');
SET @s:=IF(@c=0,"ALTER TABLE `nurses` ADD COLUMN `is_laser` TINYINT(1) DEFAULT NULL",'SELECT 1'); PREPARE st FROM @s; EXECUTE st; DEALLOCATE PREPARE st;

-- اگر نصبِ قبلی این ستون را با NOT NULL DEFAULT 0 ساخته باشد، همهٔ پرستاران برای لیزر قفل می‌شوند.
-- این بلوک آن حالت را **اصلاح** می‌کند (idempotent؛ روی نصبِ درست کاری نمی‌کند).
SET @c:=(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='nurses' AND COLUMN_NAME='is_laser' AND IS_NULLABLE='NO');
SET @s:=IF(@c=1,"ALTER TABLE `nurses` MODIFY `is_laser` TINYINT(1) DEFAULT NULL",'SELECT 1'); PREPARE st FROM @s; EXECUTE st; DEALLOCATE PREPARE st;

-- توجه (پاس ۴): «خوددرمانیِ خودکارِ مقادیرِ 0 → NULL» عمداً **حذف شد**.
-- استنتاجِ «هیچ‌کس is_laser=1 ندارد ⇒ همهٔ صفرها از DEFAULT قدیمی آمده‌اند» نادرست است:
-- کلینیک ممکن است عمداً همهٔ پرستاران را 0 (غیرمجاز) کرده باشد و آن تصمیم بازنویسی می‌شد.
-- به‌جای آن، گزارشِ تشخیصی زیر وضعیت را نشان می‌دهد و تصمیم با مدیر است
-- (دستورِ دستی در docs/phase4/41-migration-backfill-instructions.md).

-- گزارش: وضعیتِ طبقه‌بندیِ لیزرِ پرستاران — تصمیم با مدیر است، نه با مهاجرت.
-- اگر همهٔ ردیف‌ها 0 باشند و هیچ 1ی نباشد، **دو تفسیر** ممکن است:
--   (الف) طبقه‌بندیِ عمدیِ مدیر (همه غیرمجاز) → دست نزنید
--   (ب) باقی‌ماندهٔ DEFAULT 0 نسخهٔ قدیمیِ این مهاجرت → باید به NULL برگردند
-- مهاجرت این دو را از هم تشخیص نمی‌دهد و عمداً هیچ‌کدام را خودکار اعمال نمی‌کند.
SELECT 'nurses_laser_classification' AS report,
       COUNT(*)                                        AS active_nurses,
       SUM(CASE WHEN is_laser IS NULL THEN 1 ELSE 0 END) AS unclassified_null,
       SUM(CASE WHEN is_laser=0     THEN 1 ELSE 0 END)   AS prohibited_zero,
       SUM(CASE WHEN is_laser=1     THEN 1 ELSE 0 END)   AS authorized_one,
       CASE WHEN SUM(CASE WHEN is_laser=1 THEN 1 ELSE 0 END)=0
                 AND SUM(CASE WHEN is_laser=0 THEN 1 ELSE 0 END)>0
            THEN 'AMBIGUOUS: همه 0 و هیچ 1 — تصمیمِ عمدی یا باقی‌ماندهٔ DEFAULT؟ دستیْ تعیین کنید'
            ELSE 'OK' END AS note
  FROM nurses WHERE is_active=1;

SELECT 'nurses_unclassified_for_laser' AS report, id, full_name
  FROM nurses WHERE is_active=1 AND is_laser IS NULL;

-- ─────────────── cw_performed_services (D6) ───────────────
SET @c:=(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='cw_performed_services' AND COLUMN_NAME='series_payment_item_id');
SET @s:=IF(@c=0,"ALTER TABLE `cw_performed_services` ADD COLUMN `series_payment_item_id` BIGINT UNSIGNED DEFAULT NULL",'SELECT 1'); PREPARE st FROM @s; EXECUTE st; DEALLOCATE PREPARE st;


-- ─────────────── cw_performed_services (D9) ───────────────
SET @c:=(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='cw_performed_services' AND COLUMN_NAME='replacement_of_performed_service_id');
SET @s:=IF(@c=0,"ALTER TABLE `cw_performed_services` ADD COLUMN `replacement_of_performed_service_id` BIGINT UNSIGNED DEFAULT NULL",'SELECT 1'); PREPARE st FROM @s; EXECUTE st; DEALLOCATE PREPARE st;


-- ─────────────── cw_visits (D7) ───────────────
SET @c:=(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='cw_visits' AND COLUMN_NAME='profile_incomplete');
SET @s:=IF(@c=0,"ALTER TABLE `cw_visits` ADD COLUMN `profile_incomplete` TINYINT(1) NOT NULL DEFAULT 0",'SELECT 1'); PREPARE st FROM @s; EXECUTE st; DEALLOCATE PREPARE st;


-- ─────────────── cw_patient_merges (D8) ───────────────
SET @c:=(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='cw_patient_merges' AND COLUMN_NAME='reversed_at');
SET @s:=IF(@c=0,"ALTER TABLE `cw_patient_merges` ADD COLUMN `reversed_at` DATETIME DEFAULT NULL",'SELECT 1'); PREPARE st FROM @s; EXECUTE st; DEALLOCATE PREPARE st;

SET @c:=(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='cw_patient_merges' AND COLUMN_NAME='reversed_by');
SET @s:=IF(@c=0,"ALTER TABLE `cw_patient_merges` ADD COLUMN `reversed_by` INT UNSIGNED DEFAULT NULL",'SELECT 1'); PREPARE st FROM @s; EXECUTE st; DEALLOCATE PREPARE st;

SET @c:=(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='cw_patient_merges' AND COLUMN_NAME='reverse_reason');
SET @s:=IF(@c=0,"ALTER TABLE `cw_patient_merges` ADD COLUMN `reverse_reason` VARCHAR(255) DEFAULT NULL",'SELECT 1'); PREPARE st FROM @s; EXECUTE st; DEALLOCATE PREPARE st;


-- توجه (D3/پاس ۳): is_laser عمداً NULL-پذیر است.
--   NULL = هنوز طبقه‌بندی نشده  → کد سخت‌گیری نمی‌کند (پرستارِ فعال کافی است)
--   0    = مجاز نیست            → مسدود
--   1    = مجازِ لیزر           → مجاز
-- پس از پرکردنِ 0/1 برای همهٔ پرستاران، تنظیمِ laser_strict_staff=1 را فعال کنید.
-- (اگر NOT NULL DEFAULT 0 بود، بلافاصله پس از این مهاجرت همهٔ پرستاران برای لیزر قفل می‌شدند.)

-- گزارش: پرستارانی که هنوز برای لیزر طبقه‌بندی نشده‌اند
SELECT 'nurses_unclassified_for_laser' AS report, id, full_name
  FROM nurses WHERE is_active=1 AND is_laser IS NULL;

-- ─────────────── ایندکس‌های کمکی (غیرِیکتا، idempotent) ───────────────
SET @c:=(SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='cw_aftercare_deliveries' AND INDEX_NAME='idx_after_claim');
SET @s:=IF(@c=0,"ALTER TABLE `cw_aftercare_deliveries` ADD INDEX `idx_after_claim` (`status`,`claim_expires_at`)",'SELECT 1'); PREPARE st FROM @s; EXECUTE st; DEALLOCATE PREPARE st;

SET @c:=(SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='cw_performed_services' AND INDEX_NAME='idx_ps_series');
SET @s:=IF(@c=0,"ALTER TABLE `cw_performed_services` ADD INDEX `idx_ps_series` (`series_payment_item_id`)",'SELECT 1'); PREPARE st FROM @s; EXECUTE st; DEALLOCATE PREPARE st;

SET @c:=(SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='cw_performed_services' AND INDEX_NAME='idx_ps_replacement');
SET @s:=IF(@c=0,"ALTER TABLE `cw_performed_services` ADD INDEX `idx_ps_replacement` (`replacement_of_performed_service_id`)",'SELECT 1'); PREPARE st FROM @s; EXECUTE st; DEALLOCATE PREPARE st;

SET @c:=(SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='cw_patient_merges' AND INDEX_NAME='idx_merge_reversed');
SET @s:=IF(@c=0,"ALTER TABLE `cw_patient_merges` ADD INDEX `idx_merge_reversed` (`reversed_at`)",'SELECT 1'); PREPARE st FROM @s; EXECUTE st; DEALLOCATE PREPARE st;


-- ─────────────── گزارشِ remediation: رضایت‌های امضاشدهٔ نامعتبر (D3) ───────────────
-- این رکوردها با ناوردایِ جدید درمان را باز نمی‌کنند و باید دوباره امضا/بازبینی شوند.
-- (همین فهرست در صفحهٔ «قالب‌های رضایت» هم نمایش داده می‌شود.)
SELECT 'consent_remediation' AS report, c.id, c.patient_id, c.performed_service_id, c.status,
       CASE WHEN c.body_snapshot IS NULL OR c.body_snapshot='' THEN 'no_body'
            WHEN c.signature_data IS NULL OR c.signature_data='' OR c.signature_data='data:,' THEN 'no_signature'
            WHEN c.ack IS NULL OR c.ack<>1 THEN 'no_ack'
            WHEN c.signed_at IS NULL THEN 'no_signed_at'
            WHEN c.template_key IS NOT NULL AND c.template_key<>'' AND (c.template_version IS NULL OR c.template_version=0) THEN 'no_version_snapshot'
            ELSE 'ok' END AS defect
  FROM cw_consents c
 WHERE c.status='signed'
   AND ( c.body_snapshot IS NULL OR c.body_snapshot=''
      OR c.signature_data IS NULL OR c.signature_data='' OR c.signature_data='data:,'
      OR c.ack IS NULL OR c.ack<>1
      OR c.signed_at IS NULL
      OR (c.template_key IS NOT NULL AND c.template_key<>'' AND (c.template_version IS NULL OR c.template_version=0)) );

-- ─────────────── گزارشِ تشخیصی: تحویل‌های مراقبتِ گیرکرده در queued (D2) ───────────────
SELECT 'stale_aftercare_claims' AS report, id, performed_service_id, stage, status, retry_count, updated_at
  FROM cw_aftercare_deliveries
 WHERE status='queued'
   AND (claim_expires_at IS NULL OR claim_expires_at < NOW());

-- ─────────────── گزارشِ تشخیصی: تکراریِ تحویل per service/stage (پیش از هر UNIQUE آینده) ───────────────
SELECT 'aftercare_dupe_check' AS report, performed_service_id, stage, COUNT(*) AS n
  FROM cw_aftercare_deliveries
 GROUP BY performed_service_id, stage HAVING COUNT(*) > 1;

SET FOREIGN_KEY_CHECKS = 1;
-- پایانِ مهاجرتِ 30k
