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
This commit is contained in:
Kazem Alghasi 2026-08-05 22:46:31 +03:30
parent f5917c34c2
commit 8d228993f9
22 changed files with 407 additions and 465 deletions

View File

@ -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های جدید
---

View File

@ -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=

View File

@ -0,0 +1,98 @@
<?php
namespace App\Console\Commands;
use App\Models\SystemSetting;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Http;
class UpdateExchangeRates extends Command
{
protected $signature = 'ifnex:update-rates {--source=ecb : Data source for exchange rates (ecb or freecurrencyapi)}';
protected $description = 'Update exchange rates from external APIs';
public function handle(): int
{
$source = $this->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");
}
}

View File

@ -0,0 +1,98 @@
<?php
namespace App\Http\Controllers\Api;
use App\Models\DiscountCode;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
class DiscountCodeController
{
public function index(): JsonResponse
{
$codes = DiscountCode::where('is_active', true)
->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,
]);
}
}

View File

@ -0,0 +1,164 @@
<?php
namespace App\Http\Controllers\Api;
use App\Models\User;
use App\Models\Wallet;
use App\Services\WalletService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;
class WalletController
{
public function __construct(protected WalletService $walletService) {}
public function balance(Request $request): JsonResponse
{
$user = $request->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,
]);
}
}

View File

@ -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);

View File

@ -1,11 +0,0 @@
<?php
require 'vendor/autoload.php';
$app = require_once 'bootstrap/app.php';
$kernel = $app->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;
}

View File

@ -1,15 +0,0 @@
<?php
require 'vendor/autoload.php';
$app = require_once 'bootstrap/app.php';
$kernel = $app->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;
}
}

View File

@ -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' => [],

View File

@ -22,4 +22,6 @@ return [
'returned',
],
'currency_api_key' => env('CURRENCY_API_KEY'),
];

View File

@ -1,30 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('countries', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->integer('export_zone');
$table->integer('import_zone');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('countries');
}
};

View File

@ -1,31 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void
{
Schema::create('shipping_rates', function (Blueprint $table) {
$table->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');
}
};

View File

@ -1,62 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void
{
Schema::create('shipments', function (Blueprint $table) {
$table->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');
}
};

View File

@ -1,25 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void
{
Schema::create('shipment_items', function (Blueprint $table) {
$table->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');
}
};

View File

@ -1,26 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('system_settings', function (Blueprint $table) {
$table->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');
}
};

View File

@ -1,16 +0,0 @@
<?php
require 'vendor/autoload.php';
$app = require_once 'bootstrap/app.php';
$kernel = $app->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;

View File

@ -1,24 +0,0 @@
<?php
require 'vendor/autoload.php';
$app = require_once 'bootstrap/app.php';
$kernel = $app->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');

View File

@ -1,35 +0,0 @@
<?php
require 'C:/xampp/htdocs/IFNEX-Logistics/04_Laravel/vendor/autoload.php';
$reader = new \PhpOffice\PhpSpreadsheet\IOFactory();
$spreadsheet = $reader->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;
}

View File

@ -1,13 +1,23 @@
<?php
use App\Http\Controllers\Api\DiscountCodeController;
use App\Http\Controllers\Api\PricingController;
use App\Http\Controllers\Api\TrackController;
use App\Http\Controllers\Api\WalletController;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use App\Http\Middleware\ApiKeyMiddleware;
Route::middleware([ApiKeyMiddleware::class])->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']);

View File

@ -1,13 +0,0 @@
<?php
require 'vendor/autoload.php';
use PhpOffice\PhpSpreadsheet\IOFactory;
$file = '../01_Documents/Data entry 2026-06-28.xlsx';
$spreadsheet = IOFactory::load($file);
$sheetNames = $spreadsheet->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;

View File

@ -1,77 +0,0 @@
<?php
require 'vendor/autoload.php';
$app = require_once 'bootstrap/app.php';
$kernel = $app->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;
}

View File

@ -1,77 +0,0 @@
<?php
require 'vendor/autoload.php';
$app = require_once 'bootstrap/app.php';
$kernel = $app->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;
}