-- ============================================================================
--  2026_sprint30o_phase4_final_acceptance.sql — فاز ۴ / ترمیمِ نهاییِ پذیرش
--  ADDITIVE فقط / idempotent / بدونِ DELIMITER / بدونِ stored procedure /
--  سازگار با cPanel / غیرِمخرب / بدونِ حدسِ دادهٔ بالینی / بدونِ تغییرِ تنظیمات و اسرار.
--  پیش‌نیاز: پس از ...30k → 30l → 30m → 30n اجرا شود. اجرای چندباره بی‌خطر است.
--
--  ⚠ ترتیبِ اجرا: بخشِ (۱-الف) **گزارشِ تکراری‌ها** را می‌دهد. اگر ردیفی برگشت،
--     ابتدا آن‌ها را تعیینِ تکلیف کنید؛ کلیدِ یکتا در (۱-ج) فقط وقتی ساخته می‌شود
--     که تکراری‌ای باقی نمانده باشد و در غیرِ این صورت مهاجرت **بی‌صدا رد نمی‌شود**،
--     بلکه گزارشِ (۱-د) صریحاً اعلام می‌کند که کلید ساخته نشد.
-- ============================================================================
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- مقادیر عملیاتی جدید مانند replacement_scheduling_required از طول اولیهٔ ۲۴
-- کاراکتر بلندترند. این تغییر هم روی نصب تازه و هم روی ارتقای نصب قبلی لازم است.
SET @task_type_len := (SELECT CHARACTER_MAXIMUM_LENGTH FROM information_schema.COLUMNS
                        WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='cw_call_tasks'
                          AND COLUMN_NAME='task_type' LIMIT 1);
SET @s := IF(@task_type_len IS NOT NULL AND @task_type_len < 64,
  "ALTER TABLE `cw_call_tasks` MODIFY COLUMN `task_type` VARCHAR(64) NOT NULL DEFAULT 'followup'",
  'SELECT 1');
PREPARE st FROM @s; EXECUTE st; DEALLOCATE PREPARE st;

-- جابه‌جایی نوبت، تاریخ جلسهٔ متصل را هم در همان تراکنش به‌روز می‌کند؛ بنابراین
-- cw_performed_services باید مانند سایر موجودیت‌های قابل‌ویرایش مهرِ updated_at داشته باشد.
SET @c := (SELECT COUNT(*) FROM information_schema.COLUMNS
            WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='cw_performed_services'
              AND COLUMN_NAME='updated_at');
SET @s := IF(@c=0,
  "ALTER TABLE `cw_performed_services` ADD COLUMN `updated_at` DATETIME DEFAULT NULL AFTER `created_at`",
  'SELECT 1');
PREPARE st FROM @s; EXECUTE st; DEALLOCATE PREPARE st;

-- ─────────── (۱) یکتاییِ الزامِ عکسِ per-service ───────────

-- (۱-الف) گزارشِ تکراری‌های موجود — پیش از هر تغییر
SELECT 'duplicate_per_service_photo_requirements' AS report,
       performed_service_id, phase, COUNT(*) AS rows_found,
       GROUP_CONCAT(id ORDER BY id) AS requirement_ids,
       SUM(CASE WHEN status='pending' THEN 1 ELSE 0 END) AS pending_rows,
       SUM(CASE WHEN status='done'    THEN 1 ELSE 0 END) AS done_rows,
       'تعیینِ تکلیفِ دستی لازم است — کلیدِ یکتا تا رفعِ این موارد ساخته نمی‌شود' AS note
  FROM cw_photo_requirements
 WHERE performed_service_id IS NOT NULL
 GROUP BY performed_service_id, phase
HAVING COUNT(*) > 1;

-- (۱-ب) شمارشِ تکراری‌ها (مبنای تصمیمِ ساختِ کلید)
SET @dups := (SELECT COUNT(*) FROM (
    SELECT performed_service_id, phase
      FROM cw_photo_requirements
     WHERE performed_service_id IS NOT NULL
     GROUP BY performed_service_id, phase
    HAVING COUNT(*) > 1
) AS d);

-- (۱-ج) کلیدِ یکتا فقط در نبودِ تکراری.
--       MySQL چند NULL را در کلیدِ یکتا مجاز می‌داند، پس ردیف‌های legacy با
--       performed_service_id=NULL کنارِ هم باقی می‌مانند و این کلید آن‌ها را محدود نمی‌کند.
--       مدلِ واقعی: «نوعِ الزام» ستونِ جدایی ندارد و phase همان تمایزِ ساختاری است.
SET @has := (SELECT COUNT(*) FROM information_schema.STATISTICS
              WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='cw_photo_requirements'
                AND INDEX_NAME='uniq_photoreq_ps_phase');
SET @s := IF(@has=0 AND @dups=0,
  "ALTER TABLE `cw_photo_requirements` ADD UNIQUE KEY `uniq_photoreq_ps_phase` (`performed_service_id`,`phase`)",
  'SELECT 1');
PREPARE st FROM @s; EXECUTE st; DEALLOCATE PREPARE st;

-- (۱-د) نتیجهٔ صریح — بی‌صدا نیست
SET @has2 := (SELECT COUNT(*) FROM information_schema.STATISTICS
               WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='cw_photo_requirements'
                 AND INDEX_NAME='uniq_photoreq_ps_phase');
SELECT 'photo_requirement_unique_key_status' AS report,
       @dups AS duplicate_groups,
       @has2 AS unique_key_installed,
       CASE WHEN @has2=1 THEN 'OK — یکتاییِ (performed_service_id, phase) فعال است'
            ELSE 'NOT INSTALLED — ابتدا گزارشِ duplicate_per_service_photo_requirements را رفع و این مهاجرت را دوباره اجرا کنید'
       END AS note;

-- ─────────── (۲) ایندکس‌های آشتی/ابطالِ مراقبت ───────────
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.STATISTICS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='cw_aftercare_deliveries' AND INDEX_NAME='idx_after_ps_status');
SET @s := IF(@c=0,"ALTER TABLE `cw_aftercare_deliveries` ADD INDEX `idx_after_ps_status` (`performed_service_id`,`status`)",'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_aftercare_deliveries' AND INDEX_NAME='idx_after_invalidated');
SET @s := IF(@c=0,"ALTER TABLE `cw_aftercare_deliveries` ADD INDEX `idx_after_invalidated` (`invalidated_at`)",'SELECT 1'); PREPARE st FROM @s; EXECUTE st; DEALLOCATE PREPARE st;

-- ─────────── (۳) نسخهٔ ساختارِ snapshotِ ادغام ───────────
-- snapshotهای پاس‌های قبل نقشهٔ دقیقِ mutated ندارند؛ کد مسیرِ سازگارِ عقب‌رو دارد.
SET @c := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='cw_patient_merges' AND COLUMN_NAME='snapshot_version');
SET @s := IF(@c=0,"ALTER TABLE `cw_patient_merges` ADD COLUMN `snapshot_version` SMALLINT NOT NULL DEFAULT 1",'SELECT 1'); PREPARE st FROM @s; EXECUTE st; DEALLOCATE PREPARE st;

-- ─────────── (۴) گزارش‌های تشخیصی ───────────

-- (الف) رکوردهای آشتی که به خدمتِ **باطل‌شده** وصل‌اند (allow_retry باید ممنوع باشد)
SELECT 'reconciliation_on_invalidated_service' AS report,
       d.id AS delivery_id, d.performed_service_id, d.status, d.retry_count,
       d.provider_message_id, ps.invalidated_at,
       'allow_retry ممنوع — فقط ثبتِ حقیقتِ تاریخی مجاز است' AS note
  FROM cw_aftercare_deliveries d
  JOIN cw_performed_services ps ON ps.id = d.performed_service_id
 WHERE ps.invalidated_at IS NOT NULL
   AND (d.status='reconciliation_required' OR d.provider_result_unknown=1
        OR d.last_error LIKE 'reconciliation_required%');

-- (ب) ناسازگاریِ تاریخِ نوبت و جلسه (باقی‌ماندهٔ پیش از رفعِ همگام‌سازی)
SELECT 'stale_appointment_session_date_mismatch' AS report,
       ps.id AS performed_service_id, ps.scheduled_date, a.id AS appointment_id,
       a.appt_date, a.appt_time, a.status AS appt_status, a.doctor_name
  FROM cw_performed_services ps
  JOIN cw_appointments a ON a.id = ps.appointment_id
 WHERE ps.status='scheduled' AND ps.scheduled_date IS NOT NULL
   AND a.appt_date IS NOT NULL AND ps.scheduled_date <> a.appt_date;

-- (ج) snapshotهای ادغام بدونِ survivor_before/after — بازگردانیِ دقیق ممکن نیست
SELECT 'merge_snapshots_missing_before_after' AS report,
       id AS merge_id, survivor_id, duplicate_id, created_at,
       CASE WHEN remap NOT LIKE '%survivor_before%' THEN 'survivor_before ندارد'
            WHEN remap NOT LIKE '%"after"%'         THEN 'after ندارد'
            ELSE 'نقشهٔ mutated ندارد (سازگاریِ عقب‌رو فعال)' END AS gap,
       'بازگردانی برای این رکوردها ممکن است بلاک شود — دستی بررسی کنید' AS note
  FROM cw_patient_merges
 WHERE (reversed_at IS NULL)
   AND (remap NOT LIKE '%survivor_before%' OR remap NOT LIKE '%"after"%' OR remap NOT LIKE '%"mutated"%');

-- (د) تسک‌های زمان‌بندیِ جایگزین — تأییدِ اینکه پس از درخواستِ ناقص واقعاً ثبت شده‌اند
SELECT 'replacement_scheduling_tasks' AS report,
       t.id AS task_id, t.patient_id, t.source_record, t.status, t.created_at, t.note
  FROM cw_call_tasks t
 WHERE t.task_type='replacement_scheduling_required'
 ORDER BY t.id DESC
 LIMIT 200;

-- (هـ) خدماتِ باطل‌شده‌ای که تسکِ زمان‌بندیِ جایگزین برایشان ثبت **نشده**
--      (درخواستِ جایگزینِ ناقص که تسکش گم شده باشد — نباید پس از این ترمیم رخ دهد)
SELECT 'invalidated_without_expected_task' AS report,
       ps.id AS performed_service_id, ps.patient_id, ps.invalidated_at, ps.correction_ref
  FROM cw_performed_services ps
 WHERE ps.invalidated_at IS NOT NULL
   AND NOT EXISTS (SELECT 1 FROM cw_performed_services r
                    WHERE r.replacement_of_performed_service_id = ps.id)
   AND NOT EXISTS (SELECT 1 FROM cw_call_tasks t
                    WHERE t.source_record = CONCAT('performed#', ps.id)
                      AND t.task_type IN ('replacement_scheduling_required','finance_review'))
 ORDER BY ps.id DESC
 LIMIT 200;

SET FOREIGN_KEY_CHECKS = 1;
-- پایانِ مهاجرتِ 30o
