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; -}