-- ============================================================ -- طلب رحلة بلا إنترنت — عبر SMS (بند 3.1 من دراسة الفرص) -- -- ‏المسار: الراكب يرسل رسالة نصية لرقمنا الثابت ← تطبيق أندرويد على -- ‏ذلك الهاتف يوجّهها إلى webhook_sms/webhook.php ← نحلّلها وننشئ رحلة -- ‏← نرد عليه برسالة، وحين يقبل سائق نرد برسالة ثانية فيها اسمه ولوحته. -- -- ‏لا مشغّل اتصالات ولا ترخيص ولا رقم قصير — شريحة عادية في هاتف عادي. -- ============================================================ -- ── ١) الوارد الخام ───────────────────────────────────────── -- ‏نحفظ كل رسالة تصل كما وصلت، قبل أي تحليل. السبب: المحلّل سيخطئ في -- ‏البداية حتماً (اللهجات، الأخطاء الإملائية، صيغ لم نتوقّعها)، وبلا -- ‏الخام لن نعرف *لماذا* أخطأ ولا كيف نحسّنه. CREATE TABLE IF NOT EXISTS `sms_inbox` ( `id` INT NOT NULL AUTO_INCREMENT, `sender` VARCHAR(32) NOT NULL COMMENT 'رقم المرسل كما وصل من الهاتف', `sender_norm` VARCHAR(32) NOT NULL COMMENT 'الرقم بعد التوحيد (بلا رموز، بمقدمة دولية)', `body` TEXT NOT NULL, `intent` ENUM('ride','payment','unknown') NOT NULL DEFAULT 'unknown', `received_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_sender_norm` (`sender_norm`), KEY `idx_received` (`received_at`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- ── ٢) طلبات الرحلة المستخرجة ─────────────────────────────── -- ‏جدول وسيط بين الرسالة والرحلة، لا اختصار له. وظيفته الأمنية أساساً: -- ‏add_ride.php يفتح مساراً داخلياً بمفتاح S2S لهذه الطلبات، وشرط المرور -- ‏وجود صف هنا بحالة 'dispatching' يطابق الراكب ونقطة الانطلاق — تماماً -- ‏كما فعلنا مع الحجز المسبق. مفتاح مسرّب وحده لا يخلق رحلة. CREATE TABLE IF NOT EXISTS `sms_ride_requests` ( `id` INT NOT NULL AUTO_INCREMENT, `inbox_id` INT DEFAULT NULL, `sender_norm` VARCHAR(32) NOT NULL, `passenger_id` VARCHAR(100) DEFAULT NULL COMMENT 'يُملأ بعد إيجاد/إنشاء الحساب', `raw_start` VARCHAR(255) DEFAULT NULL COMMENT 'النص كما كتبه الراكب', `raw_end` VARCHAR(255) DEFAULT NULL, `start_location` VARCHAR(100) DEFAULT NULL COMMENT '"lat,lng"', `end_location` VARCHAR(100) DEFAULT NULL, `start_name` VARCHAR(255) DEFAULT NULL COMMENT 'اسم المعلم المطابَق', `end_name` VARCHAR(255) DEFAULT NULL, `distance` DOUBLE DEFAULT 0, `duration` INT DEFAULT 0, `status` ENUM('pending','dispatching','dispatched','failed','unparsed') NOT NULL DEFAULT 'pending', `ride_id` VARCHAR(100) DEFAULT NULL, `attempts` TINYINT NOT NULL DEFAULT 0, `fail_reason` VARCHAR(255) DEFAULT NULL, `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_status_created` (`status`, `created_at`), KEY `idx_ride` (`ride_id`), KEY `idx_sender` (`sender_norm`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- ── ٣) الصادر ─────────────────────────────────────────────── -- ‏طابور لا إرسال مباشر. مزوّدونا الحاليون (Kazumi / Nabeh / Intaleq) -- ‏مسارات OTP لا رسائل حرة، فالمُرسِل اليوم هو نفس الهاتف المستقبِل: -- ‏يسحب من هنا كل بضع ثوانٍ ويرسل عبر شريحته. الطابور يجعل استبدال -- ‏المُرسِل لاحقاً (مشغّل، أو بوابة تجارية) تغييراً في طرف واحد. CREATE TABLE IF NOT EXISTS `sms_outbox` ( `id` INT NOT NULL AUTO_INCREMENT, `recipient` VARCHAR(32) NOT NULL, `body` TEXT NOT NULL, `context` VARCHAR(64) DEFAULT NULL COMMENT 'ride_created / driver_found / error ...', `ref_id` VARCHAR(100) DEFAULT NULL COMMENT 'رقم الرحلة أو الطلب المرتبط', `status` ENUM('pending','sending','sent','failed') NOT NULL DEFAULT 'pending', `attempts` TINYINT NOT NULL DEFAULT 0, `claimed_by` VARCHAR(64) DEFAULT NULL COMMENT 'معرّف الهاتف الذي سحب الرسالة', `claimed_at` DATETIME DEFAULT NULL, `sent_at` DATETIME DEFAULT NULL, `last_error` VARCHAR(255) DEFAULT NULL, `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), -- ‏الفهرس على (status, id) هو ما يجعل السحب رخيصاً مهما كبر الجدول. KEY `idx_status_id` (`status`, `id`), KEY `idx_claimed` (`status`, `claimed_at`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;