Add Khanegi SMS sync inventory, batch import, and rollout helpers.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-08 15:30:43 +03:30
co-authored by Cursor
parent b9a33505e4
commit 448dc444c9
11 changed files with 693 additions and 0 deletions
+202
View File
@@ -0,0 +1,202 @@
# مراحل ساده: مهاجرت SMS و کار موازی
هدف: اول داده واقعی همکار/خانگی برود داخل سوپرادمین، بعد هر دو پنل با هم کار کنند تا خودت بگویی پنل محصولات حذف شود.
---
## الف) یک نسخه پایلوت (مثلاً همکار)
جای `DOMAIN` دامنه همان نسخه را بگذار (همان دامنه داخل سوپرادمین).
### ۱) اگر قبلاً روی همکار `SUPERADMIN_SMS_*` گذاشتی، موقتاً خاموش کن
روی سرور همکار در `.env` این خطوط را کامنت یا حذف کن:
```env
# SUPERADMIN_SMS_BASE_URL=...
# SUPERADMIN_SMS_SHARED_SECRET=...
# SUPERADMIN_SMS_DOMAIN=...
```
بعد:
```bash
php artisan config:clear
php artisan config:cache
```
علت: تا قبل از مهاجرت، پنل همکار باید همان داده واقعی محلی را نشان بدهد.
### ۲) اعداد فعلی همکار را یادداشت کن
از پنل پیامک ادمین همکار:
- موجودی
- قیمت هر پارت
- وضعیت بلاک
### ۳) سوپرادمین را با کد جدید آپدیت کن
روی سرور سوپرادمین (بعد از pull/build کد جدید):
```bash
cd /var/www/superadmin/api
pm2 restart superadmin-api
```
### ۴) مهاجرت خشک (فقط نمایش، بدون نوشتن)
روی سرور سوپرادمین، با دسترسی به MySQL همان نسخه همکار:
```bash
cd /var/www/superadmin/api
SMS_IMPORT_DRY_RUN=1 \
SMS_IMPORT_DOMAIN=DOMAIN \
SMS_IMPORT_PRODUCT=hamkar \
HAMKAR_DB_HOST=IP_یا_127.0.0.1 \
HAMKAR_DB_PORT=3306 \
HAMKAR_DB_USER=USER \
HAMKAR_DB_PASSWORD='PASS' \
HAMKAR_DB_DATABASE=DBNAME \
npm run prisma:import-sms-from-product
```
اگر گفت `SKIP: sms_panel=me` → این نسخه پنل اختصاصی دارد؛ مهاجرت لازم نیست.
اگر دامنه پیدا نشد → دامنه را با همان مقدار داخل سوپرادمین چک کن.
### ۵) مهاجرت واقعی
همان دستور بدون `SMS_IMPORT_DRY_RUN=1`:
```bash
SMS_IMPORT_DOMAIN=DOMAIN \
SMS_IMPORT_PRODUCT=hamkar \
HAMKAR_DB_HOST=... \
HAMKAR_DB_PORT=3306 \
HAMKAR_DB_USER=... \
HAMKAR_DB_PASSWORD='...' \
HAMKAR_DB_DATABASE=... \
npm run prisma:import-sms-from-product
```
### ۶) در سوپرادمین چک کن
همان نسخه را باز کن؛ موجودی/تعرفه/بلاک باید با یادداشت مرحله ۲ یکی باشد.
### ۷) کد همکار را آپدیت کن و remote را روشن کن
بعد از دیپلوی کد جدید همکار، در `.env` همکار:
```env
SUPERADMIN_SMS_BASE_URL=https://adm.montzar.ir/api/remote/sms
SUPERADMIN_SMS_SHARED_SECRET=همان_رمز_سوپرادمین
# فقط اگر APP_URL دامنه درست ندارد:
# SUPERADMIN_SMS_DOMAIN=DOMAIN
```
```bash
php artisan config:clear
php artisan config:cache
```
### ۸) تست موازی
1. پنل همکار و سوپرادمین را باز کن → موجودی یکی باشد
2. در سوپرادمین شارژ کن → در همکار رفرش → باید یکی شود
3. در همکار تعرفه/بلاک عوض کن → در سوپرادمین همان را ببین
4. یک SMS تست بفرست → موجودی هر دو کم شود
---
## ب) خانگی (چندنسخه‌ای روی یک سرور)
قانون: هر نسخه = یک Tenant = یک MySQL = یک `.env` = یک `SUPERADMIN_SMS_DOMAIN`.
روی یک سرور فیزیکی ممکن است چند نصب باشد؛ هرگز یک دامنه/DB مشترک برای همه نگذار.
### ب-۱) موجودی‌گیری از سوپرادمین
```bash
cd /var/www/superadmin/api
git pull
npm run prisma:inventory-khanegi-sms
# یا:
SMS_INVENTORY_FORMAT=json SMS_INVENTORY_OUT=/tmp/khanegi-sms-inventory.json \
npm run prisma:inventory-khanegi-sms
```
خروجی `syncStatus` / `nextAction` و لیست `multiVersionHosts` را نگه دار.
### ب-۲) فایل اهداف ایمپورت
از روی موجودی، فایل واقعی بساز (از روی نمونه):
`prisma/khanegi-sms-targets.example.json` → مثلاً `prisma/khanegi-sms-targets.json`
برای هر دامنه، MySQL **همان نسخه** را بگذار. پایلوت را `"pilot": true` کن.
### ب-۳) پایلوت: dry-run سپس import
```bash
cd /var/www/superadmin/api
# فقط پایلوت، خشک
SMS_IMPORT_TARGETS=./prisma/khanegi-sms-targets.json \
SMS_IMPORT_ONLY_PILOT=1 \
SMS_IMPORT_DRY_RUN=1 \
npm run prisma:import-sms-batch
# واقعی
SMS_IMPORT_TARGETS=./prisma/khanegi-sms-targets.json \
SMS_IMPORT_ONLY_PILOT=1 \
npm run prisma:import-sms-batch
```
یا تک‌دامنه:
```bash
bash deploy/khanegi-sms-import-one.sh DOMAIN 127.0.0.1 DB_USER 'DB_PASS' DB_NAME
bash deploy/khanegi-sms-import-one.sh DOMAIN 127.0.0.1 DB_USER 'DB_PASS' DB_NAME --apply
```
بعد در UI سوپرادمین موجودی/تعرفه/بلاک را با پنل خانگی قبل از روشن کردن remote چک کن.
ایمپورت موفق `tenants.sms_synced_at` را هم پر می‌کند.
### ب-۴) روشن کردن remote فقط روی `.env` همان نسخه
مقدار `SMS_REMOTE_SHARED_SECRET` را از `/var/www/superadmin/api/.env` بردار، بعد روی سرور خانگی:
```bash
# مسیر server همان نسخه
bash /path/to/khanegi-sms-enable-remote.sh /var/www/CUSTOMER/server DOMAIN 'SHARED_SECRET'
# یا دستی:
# SUPERADMIN_SMS_BASE_URL=https://adm.montzar.ir/api/remote/sms
# SUPERADMIN_SMS_SHARED_SECRET=...
# SUPERADMIN_SMS_DOMAIN=DOMAIN
php artisan config:clear && php artisan config:cache
php artisan sms:superadmin-probe
```
### ب-۵) rollout بقیه
همان چرخه نسخه به نسخه (یا batch بدون `ONLY_PILOT`).
`skip` / `sms_panel=me` / بدون wallet را جدا نگه دار.
---
## ج) نسخه‌هایی که هنوز فعال نشده‌اند
- جدول `sms_wallets` ندارند یا خالی‌اند → اسکریپت skip می‌کند
- `sms_panel=me` → skip
- برای آن‌ها فعلاً کاری نکن تا خودت بگویی
---
## کار موازی تا اعلام تو
تا وقتی بگویی «پنل همکار/خانگی لازم نیست»:
- تغییر در **سوپرادمین** → با باز کردن پنل محصول هم دیده می‌شود (و در MySQL محلی هم mirror می‌شود)
- تغییر در **پنل همکار/خانگی** → در سوپرادمین ذخیره می‌شود
- ارسال SMS → کسر از سوپرادمین + mirror محلی
بعد از اطمینان نهایی، پنل محصولات حذف می‌شود و فقط سوپرادمین می‌ماند.
+51
View File
@@ -0,0 +1,51 @@
#!/usr/bin/env bash
# Run on the Khanegi install for ONE sold version (its own .env).
# Never point multiple installs on the same server at the same SUPERADMIN_SMS_DOMAIN.
#
# Usage:
# bash deploy/khanegi-sms-enable-remote.sh /path/to/khanegi/server DOMAIN SHARED_SECRET
set -euo pipefail
INSTALL_SERVER_DIR="${1:-}"
DOMAIN="${2:-}"
SHARED_SECRET="${3:-}"
BASE_URL="${SUPERADMIN_SMS_BASE_URL:-https://adm.montzar.ir/api/remote/sms}"
if [[ -z "$INSTALL_SERVER_DIR" || -z "$DOMAIN" || -z "$SHARED_SECRET" ]]; then
echo "Usage: $0 /path/to/khanegi/server DOMAIN SHARED_SECRET" >&2
exit 1
fi
ENV_FILE="$INSTALL_SERVER_DIR/.env"
if [[ ! -f "$ENV_FILE" ]]; then
echo "Missing .env: $ENV_FILE" >&2
exit 1
fi
backup="$ENV_FILE.bak.sms-sync.$(date +%Y%m%d%H%M%S)"
cp "$ENV_FILE" "$backup"
echo "Backup: $backup"
tmp="$(mktemp)"
grep -vE '^(SUPERADMIN_SMS_BASE_URL|SUPERADMIN_SMS_SHARED_SECRET|SUPERADMIN_SMS_DOMAIN|SUPERADMIN_REMOTE_TOKEN)=' "$ENV_FILE" > "$tmp" || true
mv "$tmp" "$ENV_FILE"
{
echo ""
echo "# SuperAdmin SMS sync — per sold version (domain=$DOMAIN)"
echo "SUPERADMIN_SMS_BASE_URL=$BASE_URL"
echo "SUPERADMIN_SMS_SHARED_SECRET=$SHARED_SECRET"
echo "SUPERADMIN_SMS_DOMAIN=$DOMAIN"
} >> "$ENV_FILE"
cd "$INSTALL_SERVER_DIR"
php artisan config:clear
php artisan config:cache || true
if php artisan list --raw 2>/dev/null | grep -q '^sms:superadmin-probe'; then
php artisan sms:superadmin-probe
else
echo "Env written. Deploy code with sms:superadmin-probe, then run: php artisan sms:superadmin-probe"
fi
echo "OK: remote enabled for domain=$DOMAIN in $ENV_FILE"
+49
View File
@@ -0,0 +1,49 @@
#!/usr/bin/env bash
# Run on SuperAdmin host. Imports one Khanegi version MySQL → SuperAdmin SmsAccount.
#
# Usage:
# bash deploy/khanegi-sms-import-one.sh DOMAIN DB_HOST DB_USER DB_PASSWORD DB_NAME [--apply]
#
# Default is dry-run. Pass --apply for real write.
set -euo pipefail
DOMAIN="${1:-}"
DB_HOST="${2:-127.0.0.1}"
DB_USER="${3:-}"
DB_PASSWORD="${4:-}"
DB_NAME="${5:-}"
MODE="${6:-}"
if [[ -z "$DOMAIN" || -z "$DB_USER" || -z "$DB_NAME" ]]; then
echo "Usage: $0 DOMAIN DB_HOST DB_USER DB_PASSWORD DB_NAME [--apply]" >&2
exit 1
fi
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
if [[ -d /var/www/superadmin/api ]]; then
API_DIR=/var/www/superadmin/api
elif [[ -f "$SCRIPT_DIR/../package.json" ]]; then
API_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
else
API_DIR="$(cd "$SCRIPT_DIR/../apps/api" && pwd)"
fi
cd "$API_DIR"
export SMS_IMPORT_DOMAIN="$DOMAIN"
export SMS_IMPORT_PRODUCT=khanegi
export SMS_IMPORT_DB_HOST="$DB_HOST"
export SMS_IMPORT_DB_PORT=3306
export SMS_IMPORT_DB_USER="$DB_USER"
export SMS_IMPORT_DB_PASSWORD="$DB_PASSWORD"
export SMS_IMPORT_DB_DATABASE="$DB_NAME"
if [[ "$MODE" == "--apply" ]]; then
unset SMS_IMPORT_DRY_RUN || true
echo "APPLY import for $DOMAIN"
else
export SMS_IMPORT_DRY_RUN=1
echo "DRY-RUN import for $DOMAIN"
fi
npm run prisma:import-sms-from-product
+8
View File
@@ -0,0 +1,8 @@
#!/usr/bin/env bash
# Convenience: inventory + write JSON on SuperAdmin host.
set -euo pipefail
cd /var/www/superadmin/api
npm run prisma:inventory-khanegi-sms
SMS_INVENTORY_FORMAT=json SMS_INVENTORY_OUT=/tmp/khanegi-sms-inventory.json \
npm run prisma:inventory-khanegi-sms
echo "JSON: /tmp/khanegi-sms-inventory.json"