diff --git a/app/Http/Controllers/Admin/AppNotificationController.php b/app/Http/Controllers/Admin/AppNotificationController.php index 7e97cb1..d8b6968 100644 --- a/app/Http/Controllers/Admin/AppNotificationController.php +++ b/app/Http/Controllers/Admin/AppNotificationController.php @@ -5,13 +5,14 @@ namespace App\Http\Controllers\Admin; use App\DB\Te\TableEngine; use App\Http\Controllers\Controller; use App\Models\AppNotification; +use App\Models\AppNotificationRead; use Illuminate\Http\Request; class AppNotificationController extends Controller { public function table(Request $request) { - $query = AppNotification::query(); + $query = AppNotification::query()->withCount('reads'); TableEngine::create($query, $request->all()) ->initSearch(['title', 'body', 'severity']) @@ -62,6 +63,36 @@ class AppNotificationController extends Controller return response()->json(true); } + /** لیست مشتریانی که این اعلان را سین کرده‌اند */ + public function readers(int $id) + { + AppNotification::findOrFail($id); + + $rows = AppNotificationRead::query() + ->where('notification_id', $id) + ->with(['customer:id,name,fname,lname,mobile']) + ->orderByDesc('read_at') + ->orderByDesc('id') + ->get(); + + $readers = $rows->map(function (AppNotificationRead $read) { + $customer = $read->customer; + $name = trim((string) ($customer?->name ?: trim(($customer?->fname ?? '') . ' ' . ($customer?->lname ?? '')))); + + return [ + 'customer_id' => $read->customer_id, + 'name' => $name !== '' ? $name : '—', + 'mobile' => $customer?->mobile, + 'read_at' => $read->read_at, + ]; + })->values(); + + return response()->json([ + 'reads_count' => $readers->count(), + 'readers' => $readers, + ]); + } + private function validatedPayload(Request $request, bool $partial = false): array { $rules = [ diff --git a/app/Http/Controllers/Single/SingleOrderController.php b/app/Http/Controllers/Single/SingleOrderController.php index 733d72e..cd87d63 100644 --- a/app/Http/Controllers/Single/SingleOrderController.php +++ b/app/Http/Controllers/Single/SingleOrderController.php @@ -1357,7 +1357,7 @@ class SingleOrderController extends Controller if ($hasApp && $hasProduct) { $source = 'app+product'; - $label = 'قیمت اپ + تب اختلاف محصول = ' . number_format($amount) . ' ریال / گرم'; + $label = 'قیمت صفحه اصلی اپلیکیشن + تب اختلاف محصول = ' . number_format($amount) . ' ریال / گرم'; } elseif ($hasApp) { $source = 'settings'; $label = ($lines[0]['label'] ?? 'اختلاف پایه اپ') . ' = ' . number_format($amount) . ' ریال'; diff --git a/app/Http/Controllers/Site/ReportController.php b/app/Http/Controllers/Site/ReportController.php index eae4cad..f92b34f 100644 --- a/app/Http/Controllers/Site/ReportController.php +++ b/app/Http/Controllers/Site/ReportController.php @@ -267,10 +267,13 @@ class ReportController extends Controller if ($config->isOutsideTradingHours()) { $config->syncAppClosedFlag(); - return $zeroResponse(); + // نمایش قیمت صفحه اصلی حتی وقتی فروشگاه بسته است — معامله همچنان از سمت اپ بسته می‌ماند + if (! $config->shouldShowHomePriceWhenClosed()) { + return $zeroResponse(); + } + } else { + Cnt::where('key', 'app_closed')->update(['value' => 'false']); } - - Cnt::where('key', 'app_closed')->update(['value' => 'false']); } /* diff --git a/app/Models/AppNotificationRead.php b/app/Models/AppNotificationRead.php index d192357..0a19986 100644 --- a/app/Models/AppNotificationRead.php +++ b/app/Models/AppNotificationRead.php @@ -23,4 +23,9 @@ class AppNotificationRead extends Model { return $this->belongsTo(AppNotification::class, 'notification_id'); } + + public function customer(): BelongsTo + { + return $this->belongsTo(Customer::class, 'customer_id'); + } } diff --git a/app/Services/BalanceReportService.php b/app/Services/BalanceReportService.php index 7ef5cec..f0af36d 100644 --- a/app/Services/BalanceReportService.php +++ b/app/Services/BalanceReportService.php @@ -768,8 +768,8 @@ class BalanceReportService $source = $feeSource ?? $this->resolveOrderFeeSourceKey(); if ($source === 'app+product') { return $direction === 2 - ? 'قیمت اپ (sell_fee) + تب اختلاف محصول' - : 'قیمت اپ (buy_fee) + تب اختلاف محصول'; + ? 'قیمت صفحه اصلی اپلیکیشن (sell_fee) + تب اختلاف محصول' + : 'قیمت صفحه اصلی اپلیکیشن (buy_fee) + تب اختلاف محصول'; } if ($source === 'settings') { return $direction === 2 diff --git a/app/Services/ConfigService.php b/app/Services/ConfigService.php index ecd1196..5277437 100644 --- a/app/Services/ConfigService.php +++ b/app/Services/ConfigService.php @@ -492,6 +492,12 @@ class ConfigService return $this->getBool('close_price', false); } + /** خارج از ساعت کاری، قیمت صفحه اصلی را نشان بده (معامله همچنان بسته) */ + public function shouldShowHomePriceWhenClosed(): bool + { + return $this->getBool('show_home_price_when_closed', false); + } + /** @return array{start: string, end: string} */ public function tradingHourBounds(): array { diff --git a/database/migrations/2026_07_24_000001_add_show_home_price_when_closed_setting.php b/database/migrations/2026_07_24_000001_add_show_home_price_when_closed_setting.php new file mode 100644 index 0000000..cb1d1fb --- /dev/null +++ b/database/migrations/2026_07_24_000001_add_show_home_price_when_closed_setting.php @@ -0,0 +1,33 @@ +where('key', 'show_home_price_when_closed')->exists()) { + DB::table('settings')->insert([ + 'key' => 'show_home_price_when_closed', + 'value' => 'false', + 'title_fa' => 'نمایش قیمت صفحه اصلی خارج از ساعت کاری', + 'type' => 'switch', + 'can_edit' => 1, + 'is_active' => 1, + 'description' => 'روشن: وقتی فروشگاه خارج از ساعت کاری است، قیمت صفحه اصلی اپ همچنان نمایش داده می‌شود و پیام «فروشگاه بسته است» نشان داده نمی‌شود. خرید/فروش همچنان بسته می‌ماند. خاموش: مثل قبل، خارج از ساعت پیام بسته بودن و قیمت صفر نشان داده می‌شود.', + 'created_at' => now(), + 'updated_at' => now(), + ]); + } + } + + public function down(): void + { + DB::table('settings')->where('key', 'show_home_price_when_closed')->delete(); + } +}; diff --git a/database/sql/deploy_settings_const.sql b/database/sql/deploy_settings_const.sql index 7abdb5e..f821ec0 100644 --- a/database/sql/deploy_settings_const.sql +++ b/database/sql/deploy_settings_const.sql @@ -215,6 +215,12 @@ INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `de SELECT 'show_gold_ounce_price', 'false', 'نمایش قیمت انس طلا', 1, 1, NULL WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'show_gold_ounce_price' LIMIT 1); +INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`) +SELECT 'show_home_price_when_closed', 'false', 'نمایش قیمت صفحه اصلی خارج از ساعت کاری', 1, 1, + 'روشن: وقتی فروشگاه خارج از ساعت کاری است، قیمت صفحه اصلی اپ همچنان نمایش داده می‌شود و پیام «فروشگاه بسته است» نشان داده نمی‌شود. خرید/فروش همچنان بسته می‌ماند. خاموش: مثل قبل، خارج از ساعت پیام بسته بودن و قیمت صفر نشان داده می‌شود.', + 'switch' +WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'show_home_price_when_closed' LIMIT 1); + INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`) SELECT 'main_product_price', 'false', 'قیمت از محصول (legacy)', 1, 1, 'از price_provider استفاده کنید' WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'main_product_price' LIMIT 1); @@ -949,7 +955,7 @@ UPDATE `settings` SET `type` = 'select', `description` = 'bale,telegram,whatsapp UPDATE `settings` SET `type` = 'switch' WHERE `key` IN ( 'chart_use_local_data', 'jibit_enabled', 'jibit_verification', 'register_jibit_verify', 'open_register', 'auto_active_customer', - 'close_price', 'show_last_price', 'show_gold_ounce_price', + 'close_price', 'show_last_price', 'show_gold_ounce_price', 'show_home_price_when_closed', 'fee_deduct_from_gold', 'require_zero_main_gold_before_other_buy', 'profit_gold_api_enabled', 'payping_round_toman', 'payping_strict_failed_withdraw', @@ -1291,6 +1297,7 @@ UPDATE `settings` SET `description` = 'آدرس سرور(های) فید قیمت UPDATE `settings` SET `description` = 'کلید یا UUID ردیف قیمت در پاسخ API تابلو. باید با محصول/ردیف موردنظر در سرویس خارجی یکسان باشد.' WHERE `key` = 'zaarbox_price_key'; UPDATE `settings` SET `description` = 'روشن: اگر API قیمت در دسترس نباشد، آخرین قیمت ذخیره‌شده در دیتابیس به کاربر نشان داده می‌شود. خاموش: در قطعی API ممکن است قیمت صفر نمایش داده شود.' WHERE `key` = 'show_last_price'; UPDATE `settings` SET `description` = 'روشن: اگر آدرس API تابلو (zaarbox_api_url) تنظیم شده باشد، قیمت انس طلا در اپ مشتری نمایش داده می‌شود. خاموش: نمایش داده نمی‌شود.' WHERE `key` = 'show_gold_ounce_price'; +UPDATE `settings` SET `description` = 'روشن: وقتی فروشگاه خارج از ساعت کاری است، قیمت صفحه اصلی اپ همچنان نمایش داده می‌شود و پیام «فروشگاه بسته است» نشان داده نمی‌شود. خرید/فروش همچنان بسته می‌ماند. خاموش: مثل قبل، خارج از ساعت پیام بسته بودن و قیمت صفر نشان داده می‌شود.' WHERE `key` = 'show_home_price_when_closed'; UPDATE `settings` SET `description` = 'روشن: خارج از بازه «ساعت شروع» تا «ساعت پایان» معامله و نمایش قیمت بسته می‌شود. خاموش: محدودیت ساعتی سراسری اعمال نمی‌شود.' WHERE `key` = 'close_price'; UPDATE `settings` SET `description` = 'ساعت شروع فعالیت روزانه. فقط وقتی «بستن خودکار خارج از ساعت کاری» روشن باشد اعمال می‌شود (مثلاً ۰۹:۰۰).' WHERE `key` = 'startDay'; UPDATE `settings` SET `description` = 'ساعت پایان فعالیت روزانه. فقط وقتی «بستن خودکار خارج از ساعت کاری» روشن باشد اعمال می‌شود (مثلاً ۲۲:۰۰).' WHERE `key` = 'endDay'; @@ -1594,6 +1601,7 @@ UPDATE `settings` SET `title_fa` = 'آدرس API سرور' WHERE `key` = 'server UPDATE `settings` SET `title_fa` = 'کارمزد درصدی (alias)' WHERE `key` = 'service_charge_percentage'; UPDATE `settings` SET `title_fa` = 'نمایش آخرین قیمت ذخیره‌شده' WHERE `key` = 'show_last_price'; UPDATE `settings` SET `title_fa` = 'نمایش قیمت انس طلا' WHERE `key` = 'show_gold_ounce_price'; +UPDATE `settings` SET `title_fa` = 'نمایش قیمت صفحه اصلی خارج از ساعت کاری' WHERE `key` = 'show_home_price_when_closed'; UPDATE `settings` SET `title_fa` = 'مسیر SSE قیمت' WHERE `key` = 'sse_path'; UPDATE `settings` SET `title_fa` = 'ساعت شروع' WHERE `key` = 'startDay'; UPDATE `settings` SET `title_fa` = 'وضعیت فروشگاه' WHERE `key` = 'store_status'; @@ -1695,7 +1703,7 @@ UPDATE `settings` SET `can_edit` = 1 WHERE `key` IN ( 'open_register', 'referral_id', 'referral_limit', 'referral_gift_on_first_order', 'register_jibit_verify', 'register_referral_bonus_message', 'register_referral_sms_to_referrer', - 'sell_fee', 'show_last_price', 'show_gold_ounce_price', 'startDay', + 'sell_fee', 'show_last_price', 'show_home_price_when_closed', 'show_gold_ounce_price', 'startDay', 'support_messenger', 'support_messenger_id', 'support_messenger_text', 'deposit_support_message_text', 'deposit_account_holder', 'deposit_card_number', 'deposit_sheba', 'text_about', 'text_rule', diff --git a/database/sql/deploy_settings_const_preserve_value_sync_can_edit.sql b/database/sql/deploy_settings_const_preserve_value_sync_can_edit.sql index b707fb7..092f798 100644 --- a/database/sql/deploy_settings_const_preserve_value_sync_can_edit.sql +++ b/database/sql/deploy_settings_const_preserve_value_sync_can_edit.sql @@ -165,6 +165,12 @@ INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `de SELECT 'show_last_price', 'true', 'نمایش آخرین قیمت DB اگر API قطع شد', 1, 1, NULL WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'show_last_price' LIMIT 1); +INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`) +SELECT 'show_home_price_when_closed', 'false', 'نمایش قیمت صفحه اصلی خارج از ساعت کاری', 1, 1, + 'روشن: وقتی فروشگاه خارج از ساعت کاری است، قیمت صفحه اصلی اپ همچنان نمایش داده می‌شود و پیام «فروشگاه بسته است» نشان داده نمی‌شود. خرید/فروش همچنان بسته می‌ماند. خاموش: مثل قبل، خارج از ساعت پیام بسته بودن و قیمت صفر نشان داده می‌شود.', + 'switch' +WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'show_home_price_when_closed' LIMIT 1); + INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`) SELECT 'main_product_price', 'false', 'قیمت از محصول (legacy)', 1, 1, 'از price_provider استفاده کنید' WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'main_product_price' LIMIT 1); @@ -820,7 +826,7 @@ UPDATE `settings` SET `type` = 'select', `description` = 'bale,telegram,whatsapp UPDATE `settings` SET `type` = 'switch' WHERE `key` IN ( 'chart_use_local_data', 'jibit_enabled', 'jibit_verification', 'register_jibit_verify', 'open_register', 'auto_active_customer', - 'close_price', 'show_last_price', + 'close_price', 'show_last_price', 'show_home_price_when_closed', 'fee_deduct_from_gold', 'send_to_tahesab', 'profit_gold_api_enabled', 'payping_round_toman', 'payping_strict_failed_withdraw', 'has_page_earn_money', 'has_page_price_trigger', 'has_page_notification', 'charge_notif', 'group_sms', 'sms_panel', 'customer_direct_sms', @@ -1178,6 +1184,7 @@ UPDATE `settings` SET `description` = 'شناسه عددی یا نام reading_m UPDATE `settings` SET `description` = 'آدرس پایه API سرویس تابلو آنلاین (بدون مسیر انتهایی). مثال: https://example.com/api' WHERE `key` = 'zaarbox_api_url'; UPDATE `settings` SET `description` = 'کلید یا UUID ردیف قیمت در پاسخ API تابلو. باید با محصول/ردیف موردنظر در سرویس خارجی یکسان باشد.' WHERE `key` = 'zaarbox_price_key'; UPDATE `settings` SET `description` = 'روشن: اگر API قیمت در دسترس نباشد، آخرین قیمت ذخیره‌شده در دیتابیس به کاربر نشان داده می‌شود. خاموش: در قطعی API ممکن است قیمت صفر نمایش داده شود.' WHERE `key` = 'show_last_price'; +UPDATE `settings` SET `description` = 'روشن: وقتی فروشگاه خارج از ساعت کاری است، قیمت صفحه اصلی اپ همچنان نمایش داده می‌شود و پیام «فروشگاه بسته است» نشان داده نمی‌شود. خرید/فروش همچنان بسته می‌ماند. خاموش: مثل قبل، خارج از ساعت پیام بسته بودن و قیمت صفر نشان داده می‌شود.' WHERE `key` = 'show_home_price_when_closed'; UPDATE `settings` SET `description` = 'روشن: خارج از بازه «ساعت شروع» تا «ساعت پایان» معامله و نمایش قیمت بسته می‌شود. خاموش: محدودیت ساعتی سراسری اعمال نمی‌شود.' WHERE `key` = 'close_price'; UPDATE `settings` SET `description` = 'ساعت شروع فعالیت روزانه. فقط وقتی «بستن خودکار خارج از ساعت کاری» روشن باشد اعمال می‌شود (مثلاً ۰۹:۰۰).' WHERE `key` = 'startDay'; UPDATE `settings` SET `description` = 'ساعت پایان فعالیت روزانه. فقط وقتی «بستن خودکار خارج از ساعت کاری» روشن باشد اعمال می‌شود (مثلاً ۲۲:۰۰).' WHERE `key` = 'endDay'; @@ -1400,7 +1407,7 @@ UPDATE `settings` SET `can_edit` = 1 WHERE `key` IN ( 'open_register', 'referral_id', 'referral_limit', 'referral_gift_on_first_order', 'register_jibit_verify', 'register_referral_bonus_message', 'register_referral_sms_to_referrer', - 'sell_fee', 'show_last_price', 'startDay', + 'sell_fee', 'show_last_price', 'show_home_price_when_closed', 'startDay', 'support_messenger', 'support_messenger_id', 'support_messenger_text', 'deposit_support_message_text', 'deposit_account_holder', 'deposit_card_number', 'deposit_sheba', 'text_about', 'text_rule', diff --git a/routes/admin.php b/routes/admin.php index 1ed7b8c..07748fe 100644 --- a/routes/admin.php +++ b/routes/admin.php @@ -53,6 +53,7 @@ Route::middleware('auth:user')->group(function (){ Route::post('app_notifications', [AdminAppNotificationController::class, 'store']); Route::put('app_notifications/{id}', [AdminAppNotificationController::class, 'update']); Route::put('app_notifications/{id}/toggle', [AdminAppNotificationController::class, 'toggleActive']); + Route::get('app_notifications/{id}/readers', [AdminAppNotificationController::class, 'readers']); Route::delete('app_notifications/{id}', [AdminAppNotificationController::class, 'destroy']); //------------ConditionalOrder Route::post('conditional_orders/table', [\App\Http\Controllers\Admin\ConditionalOrderController::class, 'table']);