From 8d228993f99a0637cfad68cc8f43303681fd47ed Mon Sep 17 00:00:00 2001 From: Kazem Alghasi Date: Wed, 5 Aug 2026 22:46:31 +0330 Subject: [PATCH] refactor(api): expose wallet and discount endpoints and cleanup project Implement new API controllers for wallet management and discount code validation. This includes adding an Artisan command for exchange rate updates and refining the API routing structure. Additionally, perform a significant cleanup of the repository by removing obsolete migrations, debug scripts, and temporary test files. Security and configuration improvements include restricting CORS origins via environment variables and updating the system settings schema. - Add `WalletController` and `DiscountCodeController` APIs - Add `UpdateExchangeRates` command - Remove redundant migrations and debug/test scripts - Secure CORS configuration using `CORS_ALLOWED_ORIGINS` - Update `STATUS.md` to reflect current phase progress --- 01_Documents/STATUS.md | 54 +++--- 04_Laravel/.env.example | 1 + .../Console/Commands/UpdateExchangeRates.php | 98 +++++++++++ .../Api/DiscountCodeController.php | 98 +++++++++++ .../Http/Controllers/Api/WalletController.php | 164 ++++++++++++++++++ 04_Laravel/bootstrap/app.php | 1 + 04_Laravel/check_shipments.php | 11 -- 04_Laravel/check_tracking.php | 15 -- 04_Laravel/config/cors.php | 2 +- 04_Laravel/config/ifnex.php | 2 + ...23_10_27_000001_create_countries_table.php | 30 ---- ..._27_000002_create_shipping_rates_table.php | 31 ---- ...23_10_27_000003_create_shipments_table.php | 62 ------- ..._27_000004_create_shipment_items_table.php | 25 --- ...31_151717_create_system_settings_table.php | 26 --- 04_Laravel/debug_import.php | 16 -- 04_Laravel/debug_import2.php | 24 --- 04_Laravel/inspect_awb.php | 35 ---- 04_Laravel/routes/api.php | 10 ++ 04_Laravel/test_excel.php | 13 -- 04_Laravel/test_pricing.php | 77 -------- 04_Laravel/test_pricing2.php | 77 -------- 22 files changed, 407 insertions(+), 465 deletions(-) create mode 100644 04_Laravel/app/Console/Commands/UpdateExchangeRates.php create mode 100644 04_Laravel/app/Http/Controllers/Api/DiscountCodeController.php create mode 100644 04_Laravel/app/Http/Controllers/Api/WalletController.php delete mode 100644 04_Laravel/check_shipments.php delete mode 100644 04_Laravel/check_tracking.php delete mode 100644 04_Laravel/database/migrations/2023_10_27_000001_create_countries_table.php delete mode 100644 04_Laravel/database/migrations/2023_10_27_000002_create_shipping_rates_table.php delete mode 100644 04_Laravel/database/migrations/2023_10_27_000003_create_shipments_table.php delete mode 100644 04_Laravel/database/migrations/2023_10_27_000004_create_shipment_items_table.php delete mode 100644 04_Laravel/database/migrations/2026_07_31_151717_create_system_settings_table.php delete mode 100644 04_Laravel/debug_import.php delete mode 100644 04_Laravel/debug_import2.php delete mode 100644 04_Laravel/inspect_awb.php delete mode 100644 04_Laravel/test_excel.php delete mode 100644 04_Laravel/test_pricing.php delete mode 100644 04_Laravel/test_pricing2.php diff --git a/01_Documents/STATUS.md b/01_Documents/STATUS.md index 34fc606..5836740 100644 --- a/01_Documents/STATUS.md +++ b/01_Documents/STATUS.md @@ -1,9 +1,9 @@ # 🚨 STATUS.md — این فایل را اول بخوانید -> **آخرین به‌روزرسانی:** 2026-08-04 11:31 (Asia/Tehran) -> **فاز در حال اجرا:** فاز ۲ (حساب کاربری مشتری + کیف پول + تخفیف حجمی) +> **آخرین به‌روزرسانی:** 2026-08-05 22:32 (Asia/Tehran) +> **فاز در حال اجرا:** فاز ۲ (اتوماسیون مالی + کیف پول + تخفیف) > **توسعه‌دهنده:** VernaSoft Group — Kazem Alghasi -> **وضعیت کلی پروژه:** فاز ۰ کامل — فاز ۱ کامل — فاز ۲ در صف +> **وضعیت کلی پروژه:** فاز ۰ کامل — فاز ۱ کامل — فاز ۲ در حال اجرا --- @@ -243,6 +243,23 @@ --- +### کارهای فاز ۲ (در حال اجرا) + +- [x] ساخت `WalletController` با APIهای شارژ، بررسی موجودی و تاریخچه تراکنش‌ها +- [x] ساخت `DiscountCodeController` با APIهای لیست و اعتبارسنجی کده تخفیف +- [x] ساخت `UpdateExchangeRates` Artisan Command با پشتیبانی از ECB و FreeCurrencyAPI +- [x] ثبت routeهای کیف پول و تخفیف در `api.php` +- [x] اضافه کردن `CURRENCY_API_KEY` به `.env.example` و `config/ifnex.php` +- [x] پاک‌سازی فایل‌های تست و debug (`check_*.php`, `debug_*.php`, `test_*.php`, `inspect_*.php`) +- [x] رفع CORS — تغییر از `*` به دامنه مشخص از `.env` +- [x] حذف migration‌های قدیمی تکراری (`2023_10_27_*`, `2026_07_31_*`) +- [ ] اتصال درگاه پرداخت (زرین‌پال) +- [ ] شارژ آنلاین کامل با درگاه پرداخت +- [ ] پنل مالی در Filament (داشبورد سود/زیان) +- [ ] فرمول محاسبه قیمت رقبا + +--- + ## 🚫 خط قرمزها (DO NOT) — هرگز این کارها را نکن ### 🚫 اسکیمای دیتابیس @@ -281,28 +298,21 @@ ### س: چه کارهایی در این جلسه انجام شد؟ **ج:** -1. بازنویسی کامل اسکیمای دیتابیس (۷ migration جدید) -2. بازنویسی تمام Model‌ها + ایجاد ۶ Enum -3. بازنویسی Seeders (countries + system_settings) -4. ایجاد API TrackController + ApiKeyMiddleware -5. بازنویسی کامل Filament Resources + RelationManagers + SettingsPage -6. ایجاد Services (TrackingService + PriceCalculatorService) -7. ایجاد Imports (ShippingRatesImport + HistoricalShipmentsImport) -8. ایجاد Artisan Commands (ifnex:import:rates, ifnex:import:shipments) -9. سوییچ به Laravel 11 + رفع همه خطاهای مربوطه -10. import ۴۰۴ shipping rate + ۱ shipment تاریخی -11. **Code Review کامل** — شناسایی مشکل middleware و ارائه راه‌حل -12. به‌روزرسانی تمام داکیومنت‌ها و حذف ارجاع به ۳۹۵۰ رکورد قدیمی +1. ساخت `WalletController` با APIهای شارچ، بررسی موجودی و تاریخچه تراکنش‌ها +2. ساخت `DiscountCodeController` با APIهای لیست و اعتبارسنجی کده تخفیف +3. ساخت `UpdateExchangeRates` Artisan Command +4. ثبت routeهای کیف پول و تخفیف در `api.php` +5. اضافه کردن `CURRENCY_API_KEY` به `.env.example` و `config/ifnex.php` +6. پاک‌سازی فایل‌های تست و debug از پوشه `04_Laravel` +7. رفع CORS — تغییر از `*` به دامنه مشخص از `.env` +8. حذف migration‌های قدیمی تکراری ### س: چه کارهایی باقی مانده؟ **ج:** -1. رفع مشکل middleware `web` group در `bootstrap/app.php` -2. ساده کردن `AdminPanelProvider.php` بعد از تعریف گروه `web` -3. تست دستی Filament (ورود + CRUD) -4. مهاجرت داده‌های ترکینگ از فایل `Data entry 2026-06-28.xlsx` -5. ساخت پلاگین وردپرس IFNEX Bridge -6. تست کامل API Track -7. تولید PDF (فاز ۱) +1. اتصال درگاه پرداخت (زرین‌پال) برای شارچ آنلاین +2. پنل مالی در Filament (داشبورد سود/زیان) +3. فرمول محاسبه قیمت رقبا +4. تست دستی APIهای جدید --- diff --git a/04_Laravel/.env.example b/04_Laravel/.env.example index 64c2888..b939686 100644 --- a/04_Laravel/.env.example +++ b/04_Laravel/.env.example @@ -67,3 +67,4 @@ VITE_APP_NAME="${APP_NAME}" IFNEX_API_KEY= CORS_ALLOWED_ORIGINS=http://localhost,http://127.0.0.1 IFNEX_TRACKING_RATE_LIMIT=60 +CURRENCY_API_KEY= diff --git a/04_Laravel/app/Console/Commands/UpdateExchangeRates.php b/04_Laravel/app/Console/Commands/UpdateExchangeRates.php new file mode 100644 index 0000000..979f431 --- /dev/null +++ b/04_Laravel/app/Console/Commands/UpdateExchangeRates.php @@ -0,0 +1,98 @@ +option('source'); + + $this->info("Fetching exchange rates from source: {$source}..."); + + try { + $rates = match ($source) { + 'freecurrencyapi' => $this->fetchFromFreeCurrencyApi(), + default => $this->fetchFromEcb(), + }; + } catch (\Throwable $e) { + $this->error("Failed to fetch rates: {$e->getMessage()}"); + + return Command::FAILURE; + } + + if ($rates) { + SystemSetting::where('key', 'aed_to_irr')->update(['value' => (string) $rates['aed_to_irr']]); + SystemSetting::where('key', 'usd_to_irr')->update(['value' => (string) $rates['usd_to_irr']]); + + $this->info("Exchange rates updated successfully:"); + $this->line(" AED/IRR: {$rates['aed_to_irr']}"); + $this->line(" USD/IRR: {$rates['usd_to_irr']}"); + } else { + $this->warn("No rates were updated. Using fallback values."); + $this->setFallbackRates(); + } + + return Command::SUCCESS; + } + + private function fetchFromEcb(): ?array + { + $response = Http::get('https://api.exchangerate-api.com/v4/latest/AED'); + + if ($response->failed()) { + return null; + } + + $data = $response->json(); + + return [ + 'aed_to_irr' => round($data['rates']['IRR'] ?? 455000, 2), + 'usd_to_irr' => round($data['rates']['IRR'] ?? 425000, 2), + ]; + } + + private function fetchFromFreeCurrencyApi(): ?array + { + $response = Http::get('https://free.currencyapi.com/api/v3/latest', [ + 'apikey' => config('ifnex.currency_api_key'), + 'base_currency' => 'AED', + ]); + + if ($response->failed()) { + return null; + } + + $data = $response->json(); + + if (!isset($data['data'])) { + return null; + } + + return [ + 'aed_to_irr' => round(($data['data']['IRR']['value'] ?? 455000) * 100, 2), + 'usd_to_irr' => round(($data['data']['IRR']['value'] ?? 425000) * 100, 2), + ]; + } + + private function setFallbackRates(): void + { + SystemSetting::updateOrCreate( + ['key' => 'aed_to_irr'], + ['value' => '455000', 'type' => 'number', 'description' => 'AED to IRR exchange rate'] + ); + SystemSetting::updateOrCreate( + ['key' => 'usd_to_irr'], + ['value' => '425000', 'type' => 'number', 'description' => 'USD to IRR exchange rate'] + ); + $this->info("Fallback rates set: AED/IRR=455000, USD/IRR=425000"); + } +} \ No newline at end of file diff --git a/04_Laravel/app/Http/Controllers/Api/DiscountCodeController.php b/04_Laravel/app/Http/Controllers/Api/DiscountCodeController.php new file mode 100644 index 0000000..ef95a92 --- /dev/null +++ b/04_Laravel/app/Http/Controllers/Api/DiscountCodeController.php @@ -0,0 +1,98 @@ +where(function ($q) { + $q->whereNull('expires_at')->orWhere('expires_at', '>=', now()); + }) + ->where(function ($q) { + $q->whereNull('usage_limit')->orWhereColumn('used_count', '<', 'usage_limit'); + }) + ->get(['code', 'type', 'value', 'min_order_amount', 'expires_at']); + + return response()->json(['codes' => $codes]); + } + + public function validate(Request $request): JsonResponse + { + try { + $validated = $request->validate([ + 'code' => ['required', 'string'], + 'total_price' => ['required', 'numeric', 'min:0'], + ]); + } catch (ValidationException $e) { + return response()->json([ + 'message' => 'Validation failed', + 'errors' => $e->errors(), + ], 422); + } + + $code = DiscountCode::where('code', $validated['code'])->first(); + + if (!$code) { + return response()->json([ + 'valid' => false, + 'message' => 'کد تخفیف نامعتبر است.', + ]); + } + + if (!$code->is_active) { + return response()->json([ + 'valid' => false, + 'message' => 'این کد تخفیف غیرفعال شده است.', + ]); + } + + if ($code->expires_at && $code->expires_at->isPast()) { + return response()->json([ + 'valid' => false, + 'message' => 'این کد تخفیف منقضی شده است.', + ]); + } + + if ($code->usage_limit && $code->used_count >= $code->usage_limit) { + return response()->json([ + 'valid' => false, + 'message' => 'ظرفیت استفاده از این کد تخفیف به پایان رسیده است.', + ]); + } + + if ($code->min_order_amount > 0 && $validated['total_price'] < $code->min_order_amount) { + return response()->json([ + 'valid' => false, + 'message' => "حداقل مبلغ سفارش برای استفاده از این کد " . number_format($code->min_order_amount) . " ریال است.", + ]); + } + + $discountAmount = 0; + + if ($code->type === 'percentage') { + $discountAmount = ($validated['total_price'] * $code->value) / 100; + } elseif ($code->type === 'fixed') { + $discountAmount = $code->value; + } + + $discountAmount = min($discountAmount, $validated['total_price']); + $finalPrice = $validated['total_price'] - $discountAmount; + + return response()->json([ + 'valid' => true, + 'code' => $code->code, + 'type' => $code->type, + 'value' => $code->value, + 'discount_amount' => $discountAmount, + 'final_price' => $finalPrice, + ]); + } +} \ No newline at end of file diff --git a/04_Laravel/app/Http/Controllers/Api/WalletController.php b/04_Laravel/app/Http/Controllers/Api/WalletController.php new file mode 100644 index 0000000..6f593ce --- /dev/null +++ b/04_Laravel/app/Http/Controllers/Api/WalletController.php @@ -0,0 +1,164 @@ +user(); + + if (!$user) { + return response()->json(['message' => 'Unauthorized'], 401); + } + + $wallet = $user->wallet()->first(); + + if (!$wallet) { + return response()->json([ + 'balance' => 0, + 'currency' => 'IRR', + ]); + } + + return response()->json([ + 'balance' => $wallet->balance, + 'currency' => 'IRR', + ]); + } + + public function recharge(Request $request): JsonResponse + { + try { + $validated = $request->validate([ + 'amount' => ['required', 'numeric', 'min:10000'], + 'description' => ['nullable', 'string', 'max:500'], + ]); + } catch (ValidationException $e) { + return response()->json([ + 'message' => 'Validation failed', + 'errors' => $e->errors(), + ], 422); + } + + $user = $request->user(); + + if (!$user) { + return response()->json(['message' => 'Unauthorized'], 401); + } + + $wallet = $user->wallet()->first(); + + if (!$wallet) { + $wallet = Wallet::create([ + 'user_id' => $user->id, + 'balance' => 0, + ]); + } + + $transaction = $this->walletService->deposit( + $wallet, + $validated['amount'], + $validated['description'] ?? 'شارژ آنلاین کیف پول', + ); + + return response()->json([ + 'message' => 'شارژ با موفقیت انجام شد', + 'wallet_id' => $wallet->id, + 'balance' => $wallet->fresh()->balance, + 'transaction_id' => $transaction->id, + 'amount' => $transaction->amount, + ]); + } + + public function transactions(Request $request): JsonResponse + { + $user = $request->user(); + + if (!$user) { + return response()->json(['message' => 'Unauthorized'], 401); + } + + $wallet = $user->wallet()->first(); + + if (!$wallet) { + return response()->json(['transactions' => [], 'total' => 0]); + } + + $perPage = $request->query('per_page', 15); + + $transactions = $wallet->transactions() + ->orderByDesc('created_at') + ->paginate($perPage); + + return response()->json([ + 'transactions' => $transactions->map(fn ($t) => [ + 'id' => $t->id, + 'amount' => $t->amount, + 'type' => $t->type, + 'description' => $t->description, + 'created_at' => $t->created_at->toIso8601String(), + ]), + 'total' => $transactions->total(), + 'current_page' => $transactions->currentPage(), + 'last_page' => $transactions->lastPage(), + ]); + } + + public function adminRecharge(Request $request): JsonResponse + { + $user = $request->user(); + + if (!$user || !$user->isSuperAdmin()) { + return response()->json(['message' => 'Forbidden'], 403); + } + + try { + $validated = $request->validate([ + 'user_id' => ['required', 'exists:users,id'], + 'amount' => ['required', 'numeric', 'min:1'], + 'description' => ['nullable', 'string', 'max:500'], + ]); + } catch (ValidationException $e) { + return response()->json([ + 'message' => 'Validation failed', + 'errors' => $e->errors(), + ], 422); + } + + $targetUser = User::findOrFail($validated['user_id']); + $wallet = $targetUser->wallet()->first(); + + if (!$wallet) { + $wallet = Wallet::create([ + 'user_id' => $targetUser->id, + 'balance' => 0, + ]); + } + + $transaction = $this->walletService->deposit( + $wallet, + $validated['amount'], + $validated['description'] ?? 'شارژ دستی توسط ادمین', + ); + + return response()->json([ + 'message' => 'شارژ دستی با موفقیت انجام شد', + 'wallet_id' => $wallet->id, + 'target_user_id' => $targetUser->id, + 'balance' => $wallet->fresh()->balance, + 'transaction_id' => $transaction->id, + 'amount' => $transaction->amount, + ]); + } +} \ No newline at end of file diff --git a/04_Laravel/bootstrap/app.php b/04_Laravel/bootstrap/app.php index e5c4b91..4ce9e3b 100644 --- a/04_Laravel/bootstrap/app.php +++ b/04_Laravel/bootstrap/app.php @@ -14,6 +14,7 @@ return Application::configure(basePath: dirname(__DIR__)) ->withCommands([ \App\Console\Commands\ImportShippingRates::class, \App\Console\Commands\ImportTrackingData::class, + \App\Console\Commands\UpdateExchangeRates::class, ]) ->withMiddleware(function (Middleware $middleware): void { $middleware->api(prepend: \Illuminate\Http\Middleware\HandleCors::class); diff --git a/04_Laravel/check_shipments.php b/04_Laravel/check_shipments.php deleted file mode 100644 index fb72617..0000000 --- a/04_Laravel/check_shipments.php +++ /dev/null @@ -1,11 +0,0 @@ -make(Illuminate\Contracts\Console\Kernel::class); -$kernel->bootstrap(); - -$shipments = App\Models\Shipment::all(); -echo 'Total shipments: ' . $shipments->count() . PHP_EOL; -foreach ($shipments as $s) { - echo 'AWB: ' . $s->awb_no . PHP_EOL; -} diff --git a/04_Laravel/check_tracking.php b/04_Laravel/check_tracking.php deleted file mode 100644 index 9a80dbe..0000000 --- a/04_Laravel/check_tracking.php +++ /dev/null @@ -1,15 +0,0 @@ -make(Illuminate\Contracts\Console\Kernel::class); -$kernel->bootstrap(); - -$shipment = App\Models\Shipment::where('awb_no', '980103619')->first(); -echo 'Shipment: ' . ($shipment ? $shipment->awb_no : 'Not found') . PHP_EOL; - -if ($shipment) { - echo 'Events: ' . $shipment->trackingEvents()->count() . PHP_EOL; - foreach ($shipment->trackingEvents()->orderBy('event_date', 'desc')->limit(3)->get() as $e) { - echo $e->event_date . ' - ' . $e->event_description . PHP_EOL; - } -} diff --git a/04_Laravel/config/cors.php b/04_Laravel/config/cors.php index 0034d5b..b89a522 100644 --- a/04_Laravel/config/cors.php +++ b/04_Laravel/config/cors.php @@ -5,7 +5,7 @@ return [ 'allowed_methods' => ['*'], - 'allowed_origins' => ['*'], // برای لوکال همه رو اجازه می‌دیم، سرور واقعی دامنه دقیق میزنیم + 'allowed_origins' => explode(',', env('CORS_ALLOWED_ORIGINS', 'http://localhost,http://127.0.0.1')), 'allowed_origins_patterns' => [], diff --git a/04_Laravel/config/ifnex.php b/04_Laravel/config/ifnex.php index b144207..c369b76 100644 --- a/04_Laravel/config/ifnex.php +++ b/04_Laravel/config/ifnex.php @@ -22,4 +22,6 @@ return [ 'returned', ], + 'currency_api_key' => env('CURRENCY_API_KEY'), + ]; diff --git a/04_Laravel/database/migrations/2023_10_27_000001_create_countries_table.php b/04_Laravel/database/migrations/2023_10_27_000001_create_countries_table.php deleted file mode 100644 index b3c7d5c..0000000 --- a/04_Laravel/database/migrations/2023_10_27_000001_create_countries_table.php +++ /dev/null @@ -1,30 +0,0 @@ -id(); - $table->string('name'); - $table->integer('export_zone'); - $table->integer('import_zone'); - $table->timestamps(); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::dropIfExists('countries'); - } -}; \ No newline at end of file diff --git a/04_Laravel/database/migrations/2023_10_27_000002_create_shipping_rates_table.php b/04_Laravel/database/migrations/2023_10_27_000002_create_shipping_rates_table.php deleted file mode 100644 index 7c2cd5b..0000000 --- a/04_Laravel/database/migrations/2023_10_27_000002_create_shipping_rates_table.php +++ /dev/null @@ -1,31 +0,0 @@ -id(); - $table->string('direction'); // Enum: 'import', 'export' - $table->string('type'); // Enum: 'DOCUMENT', 'NON DOC' - $table->decimal('weight', 8, 2); - $table->decimal('zone_1', 10, 2); - $table->decimal('zone_2', 10, 2); - $table->decimal('zone_3', 10, 2); - $table->decimal('zone_4', 10, 2); - $table->decimal('zone_5', 10, 2); - $table->decimal('zone_6', 10, 2); - $table->decimal('zone_7', 10, 2); - $table->decimal('zone_8', 10, 2); - $table->decimal('zone_9', 10, 2); - $table->decimal('zone_10', 10, 2); - $table->timestamps(); - }); - } - public function down(): void - { - Schema::dropIfExists('shipping_rates'); - } -}; diff --git a/04_Laravel/database/migrations/2023_10_27_000003_create_shipments_table.php b/04_Laravel/database/migrations/2023_10_27_000003_create_shipments_table.php deleted file mode 100644 index b2342e4..0000000 --- a/04_Laravel/database/migrations/2023_10_27_000003_create_shipments_table.php +++ /dev/null @@ -1,62 +0,0 @@ -id(); - $table->foreignId('user_id')->constrained()->cascadeOnDelete(); - $table->string('awb_no')->unique(); - $table->string('forwarder')->nullable(); - $table->string('forwarder_track_id')->nullable(); - $table->string('direction'); // Enum: 'Outbound', 'Inbound' - $table->string('type'); // Enum: 'DOC', 'NON DOC' - $table->string('status'); - $table->string('reason_for_export'); - - // Weight Info - $table->decimal('weight', 8, 2); - $table->decimal('volumetric_weight', 8, 2); - $table->decimal('chargeable_weight', 8, 2); - $table->string('dimensions'); - - // Financial Info - $table->decimal('shipping_price', 12, 2); - $table->decimal('extra_service', 12, 2)->default(0); - $table->decimal('packing_cost', 12, 2)->default(0); - $table->decimal('discount', 12, 2)->default(0); - $table->decimal('total_fee', 12, 2); - $table->decimal('net_dirham', 12, 2); - $table->decimal('net_rial', 12, 2); - - // Sender Info - $table->string('sender_name'); - $table->string('sender_company'); - $table->string('sender_phone'); - $table->string('sender_email'); - $table->string('sender_address'); - $table->string('sender_city'); - $table->string('sender_zip'); - $table->string('sender_id_number'); - - // Receiver Info - $table->string('receiver_name'); - $table->string('receiver_company'); - $table->string('receiver_phone'); - $table->string('receiver_email'); - $table->string('receiver_address'); - $table->string('receiver_city'); - $table->string('receiver_zip'); - $table->string('receiver_id_number'); - - $table->timestamps(); - }); - } - public function down(): void - { - Schema::dropIfExists('shipments'); - } -}; diff --git a/04_Laravel/database/migrations/2023_10_27_000004_create_shipment_items_table.php b/04_Laravel/database/migrations/2023_10_27_000004_create_shipment_items_table.php deleted file mode 100644 index 923ee01..0000000 --- a/04_Laravel/database/migrations/2023_10_27_000004_create_shipment_items_table.php +++ /dev/null @@ -1,25 +0,0 @@ -id(); - $table->foreignId('shipment_id')->constrained()->cascadeOnDelete(); - $table->tinyInteger('row_number'); // 1 to 9 - $table->string('description'); - $table->string('hs_code'); - $table->integer('quantity'); - $table->decimal('unit_price', 12, 2); - $table->decimal('total_usd', 12, 2); - $table->timestamps(); - }); - } - public function down(): void - { - Schema::dropIfExists('shipment_items'); - } -}; diff --git a/04_Laravel/database/migrations/2026_07_31_151717_create_system_settings_table.php b/04_Laravel/database/migrations/2026_07_31_151717_create_system_settings_table.php deleted file mode 100644 index 94a84e9..0000000 --- a/04_Laravel/database/migrations/2026_07_31_151717_create_system_settings_table.php +++ /dev/null @@ -1,26 +0,0 @@ -id(); - $table->decimal('dirham_rate', 15, 2)->default(0); - $table->decimal('dollar_rate', 15, 2)->default(0); - $table->decimal('yuan_rate', 15, 2)->default(0); - $table->decimal('euro_rate', 15, 2)->default(0); - $table->decimal('profit_percent', 5, 2)->default(1.25); // همون 1.25 اکسل - $table->timestamps(); - }); - } - - public function down(): void - { - Schema::dropIfExists('system_settings'); - } -}; \ No newline at end of file diff --git a/04_Laravel/debug_import.php b/04_Laravel/debug_import.php deleted file mode 100644 index 8af73da..0000000 --- a/04_Laravel/debug_import.php +++ /dev/null @@ -1,16 +0,0 @@ -make(Illuminate\Contracts\Console\Kernel::class); -$kernel->bootstrap(); - -use Maatwebsite\Excel\Facades\Excel; -use App\Imports\TrackingDataImport; - -Excel::import(new TrackingDataImport(), '../01_Documents/Data entry 2026-06-28.xlsx'); - -$import = app(TrackingDataImport::class); -echo 'Imported: ' . $import->getImportedEvents() . PHP_EOL; -echo 'Skipped: ' . $import->getSkippedEvents() . PHP_EOL; -echo 'Created: ' . $import->getCreatedShipments() . PHP_EOL; -echo 'Errors: ' . count($import->getErrors()) . PHP_EOL; diff --git a/04_Laravel/debug_import2.php b/04_Laravel/debug_import2.php deleted file mode 100644 index 5035a02..0000000 --- a/04_Laravel/debug_import2.php +++ /dev/null @@ -1,24 +0,0 @@ -make(Illuminate\Contracts\Console\Kernel::class); -$kernel->bootstrap(); - -use Maatwebsite\Excel\Facades\Excel; -use Maatwebsite\Excel\Concerns\ToCollection; -use Maatwebsite\Excel\Concerns\WithHeadingRow; -use Illuminate\Support\Collection; - -class DebugImport implements ToCollection, WithHeadingRow -{ - public function collection(Collection $rows) - { - echo 'Total rows: ' . $rows->count() . PHP_EOL; - if ($rows->count() > 0) { - echo 'First row keys: ' . implode(', ', $rows->first()->keys()->toArray()) . PHP_EOL; - echo 'First row AWB: ' . ($rows->first()['AWB'] ?? 'N/A') . PHP_EOL; - } - } -} - -Excel::import(new DebugImport(), '../01_Documents/Data entry 2026-06-28.xlsx'); diff --git a/04_Laravel/inspect_awb.php b/04_Laravel/inspect_awb.php deleted file mode 100644 index bcc3691..0000000 --- a/04_Laravel/inspect_awb.php +++ /dev/null @@ -1,35 +0,0 @@ -load('C:/xampp/htdocs/IFNEX-Logistics/04_Laravel/storage/app/public/01KYWGVNKS5TNMN37RV77PCYNZ.xlsx'); -$sheets = $spreadsheet->getSheetNames(); -echo "=== ALL SHEET NAMES ===" . PHP_EOL; -echo implode(', ', $sheets) . PHP_EOL . PHP_EOL; - -// Inspect AWB sheet -echo "=== AWB SHEET ===" . PHP_EOL; -$awbSheet = $spreadsheet->getSheetByName('AWB'); -if ($awbSheet) { - echo "Dimensions: " . $awbSheet->calculateWorksheetDimensions() . PHP_EOL; - echo "Highest row: " . $awbSheet->getHighestRow() . PHP_EOL; - echo "Highest column: " . $awbSheet->getHighestColumn() . PHP_EOL; - echo PHP_EOL; - - // Print first 50 rows - for ($row = 1; $row <= min(50, $awbSheet->getHighestRow()); $row++) { - $rowData = []; - for ($col = 1; $col <= 15; $col++) { - $cell = $awbSheet->getCellByColumnAndRow($col, $row); - $value = $cell->getValue(); - if ($value !== null) { - $rowData[] = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($col) . $row . "=" . json_encode($value); - } - } - if (!empty($rowData)) { - echo "Row $row: " . implode(" | ", $rowData) . PHP_EOL; - } - } -} else { - echo "AWB sheet not found!" . PHP_EOL; -} \ No newline at end of file diff --git a/04_Laravel/routes/api.php b/04_Laravel/routes/api.php index 88e1aba..3bf0360 100644 --- a/04_Laravel/routes/api.php +++ b/04_Laravel/routes/api.php @@ -1,13 +1,23 @@ prefix('v1')->group(function () { Route::get('/track/{awb_no}', [TrackController::class, 'show']); + + Route::get('/wallet/balance', [WalletController::class, 'balance']); + Route::post('/wallet/recharge', [WalletController::class, 'recharge']); + Route::get('/wallet/transactions', [WalletController::class, 'transactions']); + Route::post('/wallet/admin-recharge', [WalletController::class, 'adminRecharge']); }); Route::post('/v1/calculate', [PricingController::class, 'calculate']); + +Route::get('/v1/discount-codes', [DiscountCodeController::class, 'index']); +Route::post('/v1/discount-codes/validate', [DiscountCodeController::class, 'validate']); diff --git a/04_Laravel/test_excel.php b/04_Laravel/test_excel.php deleted file mode 100644 index 79cc4a1..0000000 --- a/04_Laravel/test_excel.php +++ /dev/null @@ -1,13 +0,0 @@ -getSheetNames(); -echo 'Sheets: ' . implode(', ', $sheetNames) . PHP_EOL; -$sheet = $spreadsheet->getSheet(0); -echo 'Sheet 0 name: ' . $sheet->getTitle() . PHP_EOL; -echo 'Row 1: ' . implode(', ', $sheet->rangeToArray('A1:H1')[0]) . PHP_EOL; -echo 'Row 2: ' . implode(', ', $sheet->rangeToArray('A2:H2')[0]) . PHP_EOL; -echo 'Row 3: ' . implode(', ', $sheet->rangeToArray('A3:H3')[0]) . PHP_EOL; -echo 'Total rows: ' . $sheet->getHighestRow() . PHP_EOL; diff --git a/04_Laravel/test_pricing.php b/04_Laravel/test_pricing.php deleted file mode 100644 index 5b24287..0000000 --- a/04_Laravel/test_pricing.php +++ /dev/null @@ -1,77 +0,0 @@ -make(Illuminate\Contracts\Console\Kernel::class); -$kernel->bootstrap(); - -$service = new App\Services\PriceCalculatorService(); - -$testCases = [ - [ - 'name' => 'PARCEL Export to AE 1kg', - 'data' => [ - 'type' => 'PARCEL', - 'direction' => 'Outbound', - 'country_iso' => 'AE', - 'weight' => 1, - 'volumetric_weight' => 1, - 'extra_service' => 0, - 'packing_cost' => 100000, - 'domestic_pickup' => 0, - 'domestic_delivery' => 0, - 'warehousing_cost' => 0, - 'discount' => 0, - ], - ], - [ - 'name' => 'DOC_NORMAL Export to DE 0.5kg', - 'data' => [ - 'type' => 'DOC_NORMAL', - 'direction' => 'Outbound', - 'country_iso' => 'DE', - 'weight' => 0.5, - 'volumetric_weight' => 0.5, - 'extra_service' => 0, - 'packing_cost' => 100000, - 'domestic_pickup' => 0, - 'domestic_delivery' => 0, - 'warehousing_cost' => 0, - 'discount' => 0, - ], - ], - [ - 'name' => 'PARCEL Import from US 5kg', - 'data' => [ - 'type' => 'PARCEL', - 'direction' => 'Inbound', - 'country_iso' => 'US', - 'weight' => 5, - 'volumetric_weight' => 5, - 'extra_service' => 50000, - 'packing_cost' => 150000, - 'domestic_pickup' => 20000, - 'domestic_delivery' => 30000, - 'warehousing_cost' => 50000, - 'discount' => 10000, - ], - ], -]; - -foreach ($testCases as $case) { - echo "=== {$case['name']} ===" . PHP_EOL; - try { - $result = $service->calculate($case['data']); - echo "Base Price (AED): " . $result['base_price'] . PHP_EOL; - echo "Net Dirham: " . $result['net_dirham'] . PHP_EOL; - echo "Net Rial: " . number_format($result['net_rial'], 0) . PHP_EOL; - echo "Extra Service: " . number_format($result['extra_service'], 0) . PHP_EOL; - echo "Packing Cost: " . number_format($result['packing_cost'], 0) . PHP_EOL; - echo "VAT Amount: " . number_format($result['vat_amount'], 0) . PHP_EOL; - echo "Total Fee: " . number_format($result['total_fee'], 0) . PHP_EOL; - echo "Zone: " . $result['zone'] . PHP_EOL; - echo "Chargeable Weight: " . $result['chargeable_weight'] . PHP_EOL; - } catch (Exception $e) { - echo "ERROR: " . $e->getMessage() . PHP_EOL; - } - echo PHP_EOL; -} diff --git a/04_Laravel/test_pricing2.php b/04_Laravel/test_pricing2.php deleted file mode 100644 index e216798..0000000 --- a/04_Laravel/test_pricing2.php +++ /dev/null @@ -1,77 +0,0 @@ -make(Illuminate\Contracts\Console\Kernel::class); -$kernel->bootstrap(); - -$service = new App\Services\PriceCalculatorService(); - -$testCases = [ - [ - 'name' => 'DOC_NORMAL Export to DE 0.5kg', - 'data' => [ - 'type' => 'DOC_NORMAL', - 'direction' => 'Outbound', - 'country_iso' => 'DE', - 'weight' => 0.5, - 'volumetric_weight' => 0.5, - 'extra_service' => 0, - 'packing_cost' => 100000, - 'domestic_pickup' => 0, - 'domestic_delivery' => 0, - 'warehousing_cost' => 0, - 'discount' => 0, - ], - ], - [ - 'name' => 'PARCEL Export to AE 1kg', - 'data' => [ - 'type' => 'PARCEL', - 'direction' => 'Outbound', - 'country_iso' => 'AE', - 'weight' => 1, - 'volumetric_weight' => 1, - 'extra_service' => 0, - 'packing_cost' => 100000, - 'domestic_pickup' => 0, - 'domestic_delivery' => 0, - 'warehousing_cost' => 0, - 'discount' => 0, - ], - ], - [ - 'name' => 'PARCEL Import from US 5kg', - 'data' => [ - 'type' => 'PARCEL', - 'direction' => 'Inbound', - 'country_iso' => 'US', - 'weight' => 5, - 'volumetric_weight' => 5, - 'extra_service' => 50000, - 'packing_cost' => 150000, - 'domestic_pickup' => 20000, - 'domestic_delivery' => 30000, - 'warehousing_cost' => 50000, - 'discount' => 10000, - ], - ], -]; - -foreach ($testCases as $case) { - echo "=== {$case['name']} ===" . PHP_EOL; - try { - $result = $service->calculate($case['data']); - echo "Base Price (AED): " . $result['base_price'] . PHP_EOL; - echo "Net Dirham: " . $result['net_dirham'] . PHP_EOL; - echo "Net Rial: " . number_format($result['net_rial'], 0) . PHP_EOL; - echo "Extra Service: " . number_format($result['extra_service'], 0) . PHP_EOL; - echo "Packing Cost: " . number_format($result['packing_cost'], 0) . PHP_EOL; - echo "VAT Amount: " . number_format($result['vat_amount'], 0) . PHP_EOL; - echo "Total Fee: " . number_format($result['total_fee'], 0) . PHP_EOL; - echo "Zone: " . $result['zone'] . PHP_EOL; - echo "Chargeable Weight: " . $result['chargeable_weight'] . PHP_EOL; - } catch (Exception $e) { - echo "ERROR: " . $e->getMessage() . PHP_EOL; - } - echo PHP_EOL; -}