Phase 3.5 — Core Features Complete:
✅ Multi-Package Support (3.5.1) - Add shipment_packages migration and ShipmentPackage model - Add packages() relation to Shipment model - Update CustomerOrderController to accept packages[] array - Auto-calculate volumetric weight from dimensions (L*W*H/5000) - Repeater UI for adding/removing packages - Real-time summary of total weights ✅ Invoice Form for PARCEL (3.5.2) - New step in order form (only for PARCEL type) - Customs items: description, HS Code, quantity, unit price (USD) - Auto-calculate total per item and invoice total - Maximum 9 items - 5-step order form structure ✅ PDF Redesign (3.5.4) - Install picqer/php-barcode-generator - Redesign AWB with barcode, From/To, Value, Service, Type - Redesign Invoice with 9-row table, legal declaration - Redesign Label to A5 landscape with large barcode - DOC/PARCEL condition (only PARCEL has invoice) - Barcode as base64 embed ✅ Technical Fixes (3.5.5) - Add Enum casts to Shipment model (status, direction, type) - Fix deselectAfterCompletion -> deselectRecordsAfterCompletion - Migration for shipment_items (row_number, unit_price) - Migration to make name nullable - Fix Enum method names (DocNormal/DocEconomy) ✅ Tracking Test (3.5.6) - Update TrackController with status_label - Redesign tracking page with beautiful timeline - Status box with colored badge - Timeline with date, description, location, source - Carrier mappings section Documentation: - Update README, Laravel_README, Roadmap, Checklist - Add IFNEX_Meeting_Summary.md for client presentation - Update IFNEX_I18N_Strategy.md (moved to deploy phase) Phase 3.5 — 90% complete (i18n moved to deploy phase)"
This commit is contained in:
parent
1e4a8532fa
commit
b35e933475
161
01_Documents/IFNEX_Meeting_Summary.md
Normal file
161
01_Documents/IFNEX_Meeting_Summary.md
Normal file
@ -0,0 +1,161 @@
|
||||
# 📊 خلاصه جلسه ارائه IFNEX — فاز ۳ و ۳.۵
|
||||
|
||||
> **تاریخ:** 2026-08-29
|
||||
> **توسعهدهنده:** VernaSoft Group — Kazem Alghasi
|
||||
> **وضعیت:** آماده ارائه به کارفرما
|
||||
|
||||
---
|
||||
|
||||
## 🎯 خلاصه کلی
|
||||
|
||||
پروژه IFNEX در ۵ فاز توسعه داده شده که فازهای ۰، ۱، ۲، ۳ و ۳.۵ بهطور کامل تکمیل شدهاند. این سند خلاصهای از تمام قابلیتها و موارد انجامشده را برای ارائه به کارفرما ارائه میدهد.
|
||||
|
||||
---
|
||||
|
||||
## ✅ فازهای تکمیلشده
|
||||
|
||||
### فاز ۰ — پایه و زیرساخت ✅ (100%)
|
||||
- دیتابیس کامل با ۴ زون مجزا + ۳ نوع سرویس
|
||||
- ۱۷ مدل Eloquent با Enum casts
|
||||
- API ترکینگ و قیمتگذاری
|
||||
- پنل Filament با ۱۰+ Resource
|
||||
- مهاجرت ۳۹۵۰ رکورد تاریخی از اکسل
|
||||
|
||||
### فاز ۱ — پنل مشتری وردپرس ✅ (100%)
|
||||
- Bridge Auth (بدون رمز عبور)
|
||||
- دشبورد جامع مشتری (۸ تب)
|
||||
- فرم سفارش چندمرحلهای
|
||||
- API مشتری کامل
|
||||
|
||||
### فاز ۲ — پرداخت و کیف پول ✅ (100%)
|
||||
- کیف پول دیجیتال با تراکنشها
|
||||
- درگاه پرداخت Zarinpal + Mock Gateway
|
||||
- سیستم ارز چندگانه
|
||||
- تولید PDF (AWB, Invoice, Label)
|
||||
- کدهای تخفیف
|
||||
|
||||
### فاز ۳ — یکپارچهسازی ✅ (100%)
|
||||
- ۱۸ مورد اصلاحات کارفرما
|
||||
- ایمپورت/اکسپورت نرخها
|
||||
- نوتیفیکیشن دیتابیس
|
||||
- ۱۵ مورد اصلاح تست لوکال
|
||||
|
||||
### فاز ۳.۵ — اصلاحات اساسی ✅ (90%)
|
||||
|
||||
#### ۳.۵.۱ — Multi-Package ✅
|
||||
- پشتیبانی از چند بسته در یک سفارش
|
||||
- محاسبه خودکار وزن حجمی از ابعاد
|
||||
- فرمول استاندارد IATA: `L×W×H/5000`
|
||||
- رابط کاربری Repeater (افزودن/حذف بسته)
|
||||
|
||||
#### ۳.۵.۲ — فرم Invoice برای PARCEL ✅
|
||||
- مرحله جدید در فرم سفارش (فقط برای PARCEL)
|
||||
- اقلام گمرکی: شرح، HS Code، تعداد، قیمت
|
||||
- محاسبه خودکار مجموع فاکتور (USD)
|
||||
- حداکثر ۹ قلم
|
||||
|
||||
#### ۳.۵.۴ — بازطراحی PDFها ✅
|
||||
- نصب کتابخانه بارکد `picqer/php-barcode-generator`
|
||||
- **AWB:** بارکد، From/To کامل، Value، Service، Type، جدول آیتمها
|
||||
- **Invoice:** جدول ۹ ردیفه، اعلان قانونی، بارکد پایین
|
||||
- **Label:** اندازه A5 افقی، بارکد بزرگ، اطلاعات ضروری
|
||||
- شرط DOC/PARCEL (فقط PARCEL فاکتور داره)
|
||||
|
||||
#### ۳.۵.۵ — اصلاحات فنی ✅
|
||||
- Enum casts در مدل Shipment
|
||||
- Migrationهای جدید (shipment_packages, row_number, unit_price)
|
||||
- اصلاح متدهای Filament
|
||||
|
||||
#### ۳.۵.۶ — تست ترکینگ ✅
|
||||
- بازطراحی صفحه رهگیری با timeline زیبا
|
||||
- باکس وضعیت با badge رنگی
|
||||
- نمایش تاریخ، توضیحات، لوکیشن و منبع
|
||||
- بخش شرکتهای حمل
|
||||
|
||||
---
|
||||
|
||||
## 📊 آمار پروژه
|
||||
|
||||
| معیار | مقدار |
|
||||
|-------|-------|
|
||||
| تعداد مایگریشنها | ۳۱ |
|
||||
| تعداد API endpoints | ۳۰+ |
|
||||
| تعداد Filament Resources | ۱۰+ |
|
||||
| تعداد مدلها | ۱۷ |
|
||||
| تعداد شورتکدهای وردپرس | ۱۰+ |
|
||||
| تعداد کشورها با پیششماره | ۱۹۲ |
|
||||
| رکوردهای تاریخی مهاجرتشده | ۳۹۵۰ |
|
||||
| زبانهای پشتیبانیشده | ۲ (فارسی، انگلیسی) |
|
||||
| خطوط کد (تخمینی) | ۱۷۰۰۰+ |
|
||||
|
||||
---
|
||||
|
||||
## 🧪 تستهای انجامشده
|
||||
|
||||
| تست | وضعیت |
|
||||
|-----|--------|
|
||||
| لاگین مشتری (Bridge Auth) | ✅ |
|
||||
| ثبت سفارش با Multi-Package | ✅ |
|
||||
| فرم Invoice برای PARCEL | ✅ |
|
||||
| محاسبه قیمت | ✅ |
|
||||
| پرداخت با کیف پول | ✅ |
|
||||
| شارژ کیف پول | ✅ |
|
||||
| نوتیفیکیشنها | ✅ |
|
||||
| تولید PDF (AWB, Invoice, Label) | ✅ |
|
||||
| ترکینگ چندمرحلهای | ✅ |
|
||||
| پنل ادمین Filament | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 📋 موارد باقیمانده
|
||||
|
||||
### فاز ۳.۵ — باقیمانده (زمان دیپلوی)
|
||||
- **پشتیبانی چندزبانه (i18n)** — استراتژی کامل در `IFNEX_I18N_Strategy.md`
|
||||
|
||||
### فاز ۴ (آینده) — سیستم نمایندگی
|
||||
- مدل نمایندگی کامل با کمیسیون
|
||||
- پنل جداگانه برای نماینده
|
||||
- طرح مطالعاتی آماده
|
||||
|
||||
### فاز ۵ (آینده) — تجاریسازی
|
||||
- مدل پولی چندلایهای (Starter / Business / Enterprise)
|
||||
- فرمساز Drag & Drop
|
||||
- White-label branding
|
||||
- API Gateway
|
||||
|
||||
---
|
||||
|
||||
## 📁 مستندات موجود
|
||||
|
||||
| فایل | محتوا |
|
||||
|------|-------|
|
||||
| `README.md` | معرفی کلی پروژه |
|
||||
| `04_Laravel/README.md` | راهنمای بکاند |
|
||||
| `IFNEX_Roadmap.md` | نقشه راه ۵ فازی |
|
||||
| `IFNEX_Phase0_Checklist.md` | چکلیست کامل |
|
||||
| `IFNEX_File_Map.md` | نقشه فایلها |
|
||||
| `IFNEX_I18N_Strategy.md` | استراتژی چندزبانه |
|
||||
| `IFNEX_Commercial_Model.md` | مدل تجاری |
|
||||
| `DEPLOYMENT.md` | راهنمای استقرار |
|
||||
|
||||
---
|
||||
|
||||
## 🚀 قدم بعدی
|
||||
|
||||
1. **دیپلوی روی سرور** (بعد از جلسه)
|
||||
2. **پشتیبانی چندزبانه** (همراه با دیپلوی)
|
||||
3. **سیستم نمایندگی** (فاز ۴)
|
||||
4. **تجاریسازی** (فاز ۵)
|
||||
|
||||
---
|
||||
|
||||
## 📞 تماس
|
||||
|
||||
- **توسعهدهنده:** Kazem Alghasi
|
||||
- **شرکت:** VernaSoft Group
|
||||
- **ایمیل:** kazem@vernasoft.group
|
||||
- **سرور تست:** api.ifnex.vernahost.ir | ifnex.vernahost.ir
|
||||
|
||||
---
|
||||
|
||||
> 2026 VernaSoft Group. Internal use only.
|
||||
@ -16,11 +16,11 @@
|
||||
| فاز ۱ (پنل مشتری) | ✅ تکمیل شده | 100% |
|
||||
| فاز ۲ (پرداخت و کیف پول) | ✅ تکمیل شده | 100% |
|
||||
| فاز ۳ (یکپارچهسازی) | ✅ تکمیل شده | 100% |
|
||||
| **فاز ۳.۵ (اصلاحات اساسی)** | 🔄 در دست اقدام | 0% |
|
||||
| **فاز ۳.۵ (اصلاحات اساسی)** | ✅ تکمیل شده (۹۰%) | 90% |
|
||||
| فاز ۴ (سیستم نمایندگی) | 📋 مطالعاتی | 0% |
|
||||
| فاز ۵ (تجاریسازی) | 📋 مطالعاتی | 0% |
|
||||
|
||||
> **نکته:** فاز ۳.۵ شامل اصلاحات اساسی است که پس از تست لوکال شناسایی شد. این فاز قبل از ارائه نهایی به کارفرما باید تکمیل شود.
|
||||
> **نکته:** فاز ۳.۵ شامل Multi-Package، فرم Invoice، بازطراحی PDFها و تست ترکینگ است. تنها مورد باقیمانده (پشتیبانی چندزبانه i18n) به زمان دیپلوی منتقل شد.
|
||||
|
||||
---
|
||||
|
||||
@ -296,58 +296,84 @@
|
||||
|
||||
---
|
||||
|
||||
## بخش ۱۱: فاز ۳.۵ — اصلاحات اساسی 🔄
|
||||
## بخش ۱۱: فاز ۳.۵ — اصلاحات اساسی ✅
|
||||
|
||||
> ⚠️ **این بخش قبل از ارائه نهایی به کارفرما باید تکمیل شود.**
|
||||
> ✅ **این فاز قبل از ارائه نهایی به کارفرما تکمیل شد.**
|
||||
|
||||
### ۱۱.۱ — Multi-Package در فرم سفارش مشتری 🔴 ضروری
|
||||
### ۱۱.۱ — Multi-Package در فرم سفارش مشتری ✅
|
||||
|
||||
**مشکل:** بکاند و پنل ادمین از چند بسته پشتیبانی میکنند، ولی فرم سفارش مشتری فقط یه بسته میگیره.
|
||||
- [x] ساخت migration `shipment_packages`
|
||||
- [x] ساخت مدل `ShipmentPackage` با `calculateVolumetricWeight()`
|
||||
- [x] اضافهشدن رابطه `packages()` به مدل Shipment
|
||||
- [x] اصلاح API `store` در CustomerOrderController — پذیرش `packages[]`
|
||||
- [x] محاسبه خودکار وزن حجمی از ابعاد (L×W×H/5000)
|
||||
- [x] اصلاح HTML فرم — اضافهشدن Repeater بستهها در Step 1
|
||||
- [x] اصلاح `ifnex-order-form.js` — منطق Multi-Package
|
||||
- [x] اعتبارسنجی اجباری وزن واقعی و ابعاد
|
||||
- [x] خلاصه وزنها (مجموع واقعی، حجمی، قابل محاسبه)
|
||||
- [x] نرمالسازی ابعاد به فرمت `*` (مثلاً `5*6*9`)
|
||||
- [x] تست با چند بسته
|
||||
|
||||
- [ ] اصلاح `ifnex-order-form.js` — افزودن قابلیت "افزودن بسته دیگر"
|
||||
- [ ] اصلاح HTML فرم — نمایش لیست بستههای اضافهشده
|
||||
- [ ] محاسبه مجموع وزنها و قیمت نهایی
|
||||
- [ ] اصلاح `buildOrderData()` — ارسال آرایه `packages[]`
|
||||
- [ ] اصلاح API `POST /customer/orders` — پذیرش `packages[]`
|
||||
- [ ] ایجاد خودکار `ShipmentPackage` هنگام ثبت سفارش
|
||||
- [ ] تست با چند بسته
|
||||
### ۱۱.۲ — فرم Invoice برای محمولههای PARCEL ✅
|
||||
|
||||
### ۱۱.۲ — فرم Invoice برای محمولههای PARCEL 🔴 ضروری
|
||||
- [x] اضافهشدن مرحله جدید به فرم سفارش (مرحله ۳)
|
||||
- [x] نمایش این مرحله فقط وقتی نوع = PARCEL
|
||||
- [x] فیلدهای هر آیتم: شرح کالا، HS Code، تعداد، قیمت واحد
|
||||
- [x] امکان افزودن چندین آیتم (تا ۹ ردیف)
|
||||
- [x] محاسبه خودکار Total و نمایش مجموع
|
||||
- [x] ارسال `items[]` به API
|
||||
- [x] ایجاد خودکار `ShipmentItem` هنگام ثبت سفارش
|
||||
- [x] ساختار ۵ مرحلهای فرم
|
||||
- [x] تست با چند آیتم
|
||||
|
||||
**مشکل:** وقتی مشتری نوع PARCEL رو انتخاب میکنه، باید فرم اقلام گمرکی (Invoice) رو پر کنه.
|
||||
### ۱۱.۳ — پشتیبانی چندزبانه (i18n) 📋
|
||||
|
||||
- [ ] اضافهکردن مرحله جدید به فرم سفارش (مرحله ۲.۵)
|
||||
- [ ] نمایش این مرحله فقط وقتی نوع = PARCEL
|
||||
- [ ] فیلدهای هر آیتم: Description, HS Code, Quantity, Unit Price, Total
|
||||
- [ ] امکان افزودن چندین آیتم (تا ۹ ردیف)
|
||||
- [ ] محاسبه خودکار Total و نمایش مجموع
|
||||
- [ ] ارسال `items[]` به API
|
||||
- [ ] ایجاد خودکار `ShipmentItem` هنگام ثبت سفارش
|
||||
- [ ] تست با چند آیتم
|
||||
**وضعیت:** به زمان دیپلوی منتقل شد
|
||||
|
||||
### ۱۱.۳ — پشتیبانی چندزبانه (i18n) 🟡 مهم
|
||||
- [ ] استخراج تمام رشتههای متنی به فایلهای ترجمه (زمان دیپلوی)
|
||||
- [ ] استفاده از `__()` و `_e()` در وردپرس (زمان دیپلوی)
|
||||
- [ ] استفاده از `wp_localize_script` برای ارسال ترجمهها به JS (زمان دیپلوی)
|
||||
- [ ] افزودن فایلهای `.po/.mo` برای زبانهای فارسی و انگلیسی (زمان دیپلوی)
|
||||
- [ ] اصلاح CSS برای پشتیبانی RTL/LTR (زمان دیپلوی)
|
||||
|
||||
**مشکل:** سایت ۲ زبانه (فارسی + انگلیسی) هست با Polylang، ولی:
|
||||
- تمام شورتکدها و فرمها فقط فارسی هستن
|
||||
- پیامهای خطا و موفقیت فقط فارسی هستن
|
||||
> سند کامل استراتژی در `IFNEX_I18N_Strategy.md` موجود است.
|
||||
|
||||
- [ ] استخراج تمام رشتههای متنی به فایلهای ترجمه
|
||||
- [ ] استفاده از `__()` و `_e()` در وردپرس
|
||||
- [ ] استفاده از `wp_localize_script` برای ارسال ترجمهها به JS
|
||||
- [ ] افزودن فایلهای `.po/.mo` برای زبانهای فارسی و انگلیسی
|
||||
- [ ] اصلاح تمام فایلهای `ifnex-bridge/includes/*.php`
|
||||
- [ ] اصلاح `ifnex-order-form.js` (استفاده از متغیرهای ترجمه)
|
||||
- [ ] اصلاح CSS برای پشتیبانی RTL/LTR
|
||||
- [ ] تست زبان انگلیسی
|
||||
### ۱۱.۴ — بازطراحی PDFها ✅
|
||||
|
||||
### ۱۱.۴ — تست و نهاییسازی 🟡 مهم
|
||||
- [x] نصب `picqer/php-barcode-generator`
|
||||
- [x] بازطراحی AWB با بارکد، From/To کامل، Value، Service، Type
|
||||
- [x] بازطراحی Invoice با جدول ۹ ردیفه، اعلان قانونی، بارکد پایین
|
||||
- [x] بازطراحی Label با اندازه A5 افقی و بارکد بزرگ
|
||||
- [x] اضافهشدن شرط DOC/PARCEL برای Invoice
|
||||
- [x] بارکد بهصورت base64 embed
|
||||
- [x] تست تولید PDF با بارکد
|
||||
|
||||
- [ ] تست تولید PDF با بارکد روی سرور
|
||||
- [ ] تست Multi-Package
|
||||
- [ ] تست Invoice Form برای PARCEL
|
||||
- [ ] تست زبان انگلیسی
|
||||
- [ ] دیپلوی نهایی روی سرور
|
||||
- [ ] تست نهایی روی سرور
|
||||
### ۱۱.۵ — اصلاحات فنی ✅
|
||||
|
||||
- [x] اضافهشدن Enum casts به مدل Shipment (status, direction, type)
|
||||
- [x] اصلاح متد `deselectAfterCompletion` → `deselectRecordsAfterCompletion`
|
||||
- [x] Migration برای ستونهای `row_number` و `unit_price` در shipment_items
|
||||
- [x] Migration برای nullable کردن `name`
|
||||
- [x] اصلاح متدهای Enum (DocNormal/DocEconomy)
|
||||
|
||||
### ۱۱.۶ — تست ترکینگ ✅
|
||||
|
||||
- [x] اصلاح API لاراول (TrackController) با `status_label`
|
||||
- [x] بازطراحی صفحه رهگیری وردپرس با timeline زیبا
|
||||
- [x] باکس وضعیت با badge رنگی بر اساس status
|
||||
- [x] grid اطلاعات کامل (۸ فیلد)
|
||||
- [x] timeline با تاریخ، توضیحات، لوکیشن و منبع
|
||||
- [x] بخش شرکتهای حمل
|
||||
- [x] تست موفق با محموله چندمرحلهای
|
||||
|
||||
### ۱۱.۷ — تست و نهاییسازی ✅
|
||||
|
||||
- [x] تست تولید PDF با بارکد
|
||||
- [x] تست Multi-Package
|
||||
- [x] تست Invoice Form برای PARCEL
|
||||
- [x] تست ترکینگ چندمرحلهای
|
||||
- [x] تست پنل ادمین (DashboardInfoWidget)
|
||||
- [ ] دیپلوی نهایی روی سرور (بعد از جلسه)
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
> **آخرین بروزرسانی:** 2026-08-29
|
||||
> **توسعهدهنده:** VernaSoft Group — Kazem Alghasi
|
||||
> **وضعیت کلی:** فاز ۰، ۱، ۲، ۳ تکمیل شد ✅ — فاز ۳.۵ در دست اقدام 🔄
|
||||
> **وضعیت کلی:** فاز ۰، ۱، ۲، ۳ و ۳.۵ تکمیل شد ✅
|
||||
|
||||
---
|
||||
|
||||
@ -14,10 +14,12 @@
|
||||
| فاز ۱ | پنل مشتری وردپرس | ✅ تکمیل شده | 100% |
|
||||
| فاز ۲ | پرداخت و کیف پول | ✅ تکمیل شده | 100% |
|
||||
| فاز ۳ | یکپارچهسازی و بهبود | ✅ تکمیل شده | 100% |
|
||||
| **فاز ۳.۵** | **اصلاحات اساسی** | 🔄 در دست اقدام | 0% |
|
||||
| **فاز ۳.۵** | **اصلاحات اساسی** | ✅ تکمیل شده (۹۰%) | 90% |
|
||||
| فاز ۴ | سیستم نمایندگی | 📋 مطالعاتی | 0% |
|
||||
| فاز ۵ | تجاریسازی و Premium | 📋 مطالعاتی | 0% |
|
||||
|
||||
> **نکته:** فاز ۳.۵ شامل Multi-Package، فرم Invoice و تست ترکینگ است. تنها مورد باقیمانده (پشتیبانی چندزبانه i18n) به زمان دیپلوی منتقل شد.
|
||||
|
||||
---
|
||||
|
||||
## فاز ۰ — پایه و زیرساخت ✅
|
||||
@ -123,78 +125,82 @@
|
||||
|
||||
---
|
||||
|
||||
## فاز ۳.۵ — اصلاحات اساسی 🔄
|
||||
## فاز ۳.۵ — اصلاحات اساسی ✅
|
||||
|
||||
**هدف:** رفع نواقص شناساییشده پس از تست لوکال + افزودن قابلیتهای ضروری
|
||||
|
||||
> ⚠️ **این فاز قبل از ارائه نهایی به کارفرما باید تکمیل شود.**
|
||||
> ✅ **این فاز قبل از ارائه نهایی به کارفرما تکمیل شد.**
|
||||
|
||||
### ۳.۵.۱ — Multi-Package در فرم سفارش مشتری 🔴 ضروری
|
||||
### ۳.۵.۱ — Multi-Package در فرم سفارش مشتری ✅
|
||||
|
||||
**مشکل:** در حال حاضر، مشتری میتونه فقط یه بسته ثبت کنه. در صورتی که:
|
||||
- بکاند پشتیبانی از چند بسته (ShipmentPackage) رو داره ✅
|
||||
- پنل ادمین Filament هم Repeater برای بستهها داره ✅
|
||||
- ولی فرم سفارش مشتری (وردپرس) فقط یه بسته میگیره ❌
|
||||
**مشکل:** بکاند و پنل ادمین از چند بسته پشتیبانی میکردند، ولی فرم سفارش مشتری فقط یه بسته میگرفت.
|
||||
|
||||
**راه حل:**
|
||||
- اضافهکردن قابلیت "افزودن بسته دیگر" در مرحله ۱ فرم سفارش
|
||||
- هر بسته شامل: وزن واقعی، وزن حجمی، ابعاد، تعداد
|
||||
- محاسبه مجموع وزنها و قیمت نهایی
|
||||
- نمایش لیست بستههای اضافهشده قبل از تأیید نهایی
|
||||
**انجام شده:**
|
||||
- ✅ ساخت migration `shipment_packages` (جدول جدید)
|
||||
- ✅ ساخت مدل `ShipmentPackage` با متد `calculateVolumetricWeight()`
|
||||
- ✅ اضافهشدن رابطه `packages()` به مدل `Shipment`
|
||||
- ✅ اصلاح API `POST /customer/orders` برای پذیرش `packages[]`
|
||||
- ✅ محاسبه خودکار وزن حجمی از ابعاد (فرمول: `L×W×H/5000`)
|
||||
- ✅ بازطراحی فرم سفارش با Repeater UI (افزودن/حذف بسته)
|
||||
- ✅ اعتبارسنجی اجباری وزن واقعی و ابعاد
|
||||
- ✅ خلاصه وزنها (مجموع واقعی، حجمی، قابل محاسبه)
|
||||
- ✅ نرمالسازی ابعاد به فرمت `*` (مثلاً `5*6*9`)
|
||||
|
||||
**تأثیرات:**
|
||||
- اصلاح `ifnex-order-form.js` (افزودن Repeater UI)
|
||||
- اصلاح `buildOrderData()` (ارسال آرایه packages)
|
||||
- اصلاح API `POST /customer/orders` (پذیرش packages[])
|
||||
- اصلاح مدل Shipment (ایجاد خودکار ShipmentPackage)
|
||||
### ۳.۵.۲ — فرم Invoice برای محمولههای PARCEL ✅
|
||||
|
||||
### ۳.۵.۲ — فرم Invoice برای محمولههای PARCEL 🔴 ضروری
|
||||
**مشکل:** وقتی مشتری نوع PARCEL رو انتخاب میکرد، باید فرم اقلام گمرکی (Invoice) رو پر میکرد.
|
||||
|
||||
**مشکل:** وقتی مشتری نوع محموله رو PARCEL انتخاب میکنه، باید فرم Invoice (اقلام گمرکی) رو پر کنه. در حال حاضر این فرم وجود نداره.
|
||||
**انجام شده:**
|
||||
- ✅ اضافهشدن مرحله جدید به فرم سفارش (مرحله ۳ - اقلام گمرکی)
|
||||
- ✅ نمایش این مرحله فقط وقتی نوع = PARCEL
|
||||
- ✅ فیلدهای هر آیتم: شرح کالا، HS Code، تعداد، قیمت واحد (USD)
|
||||
- ✅ محاسبه خودکار مجموع هر قلم
|
||||
- ✅ خلاصه فاکتور (تعداد اقلام + مجموع USD)
|
||||
- ✅ حداکثر ۹ قلم
|
||||
- ✅ اعتبارسنجی کامل (همه فیلدها اجباری)
|
||||
- ✅ نمایش اقلام در خلاصه قیمت نهایی
|
||||
- ✅ ساختار ۵ مرحلهای فرم (۱: مسیر، ۲: تماس، ۳: اقلام، ۴: تخفیف، ۵: تأیید)
|
||||
|
||||
**راه حل:**
|
||||
- اضافهکردن مرحله جدید به فرم سفارش (مرحله ۲.۵)
|
||||
- نمایش این مرحله فقط وقتی نوع = PARCEL
|
||||
- فیلدهای هر آیتم: Description, HS Code, Quantity, Unit Price (USD), Total (USD)
|
||||
- امکان افزودن چندین آیتم (تا ۹ ردیف)
|
||||
- محاسبه خودکار Total و نمایش مجموع
|
||||
- ارسال items[] به API
|
||||
### ۳.۵.۳ — پشتیبانی چندزبانه (i18n) 📋
|
||||
|
||||
**تأثیرات:**
|
||||
- اصلاح `ifnex-order-form.js` (مرحله ۲.۵ با Repeater)
|
||||
- اصلاح `ifnex-order-form.blade.php` (HTML مرحله جدید)
|
||||
- اصلاح API `POST /customer/orders` (پذیرش items[])
|
||||
**وضعیت:** به زمان دیپلوی منتقل شد
|
||||
|
||||
### ۳.۵.۳ — پشتیبانی چندزبانه (i18n) 🟡 مهم
|
||||
این مورد به زمان دیپلوی منتقل شد تا همراه با تنظیمات Polylang روی سرور انجام بشه. سند کامل استراتژی در `IFNEX_I18N_Strategy.md` موجود است.
|
||||
|
||||
**مشکل:** سایت ۲ زبانه (فارسی + انگلیسی) هست با Polylang، ولی:
|
||||
- تمام شورتکدها و فرمها فقط فارسی هستن
|
||||
- پیامهای خطا و موفقیت فقط فارسی هستن
|
||||
- فایلهای JS پیامهای hard-coded فارسی دارن
|
||||
- فایلهای PHP رشتههای hard-coded فارسی دارن
|
||||
### ۳.۵.۴ — بازطراحی PDFها ✅
|
||||
|
||||
**راه حل:**
|
||||
- استخراج تمام رشتههای متنی به فایلهای ترجمه
|
||||
- استفاده از `__()` و `_e()` در وردپرس
|
||||
- استفاده از `__('text', 'ifnex-bridge')` در فایلهای PHP پلاگین
|
||||
- استفاده از `wp_localize_script` برای ارسال ترجمهها به JS
|
||||
- افزودن فایلهای `.po/.mo` برای زبانهای فارسی و انگلیسی
|
||||
- پشتیبانی از RTL/LTR بهصورت خودکار بر اساس زبان فعال
|
||||
- ✅ نصب `picqer/php-barcode-generator`
|
||||
- ✅ بازطراحی AWB با بارکد، From/To کامل، Value، Service، Type
|
||||
- ✅ بازطراحی Invoice با جدول ۹ ردیفه، اعلان قانونی، بارکد پایین
|
||||
- ✅ بازطراحی Label با اندازه A5 افقی و بارکد بزرگ
|
||||
- ✅ اضافهشدن شرط DOC/PARCEL برای Invoice (فقط PARCEL فاکتور داره)
|
||||
- ✅ بارکد بهصورت base64 embed (مطمئنترین روش)
|
||||
|
||||
**تأثیرات:**
|
||||
- اصلاح تمام فایلهای `ifnex-bridge/includes/*.php`
|
||||
- اصلاح `ifnex-order-form.js` (استفاده از متغیرهای ترجمه)
|
||||
- ساخت فایلهای ترجمه در `ifnex-bridge/languages/`
|
||||
- اصلاح CSS برای پشتیبانی RTL/LTR
|
||||
### ۳.۵.۵ — اصلاحات فنی ✅
|
||||
|
||||
### ۳.۵.۴ — تست و نهاییسازی 🟡 مهم
|
||||
- ✅ اضافهشدن Enum casts به مدل Shipment (status, direction, type)
|
||||
- ✅ اصلاح متد `deselectAfterCompletion` → `deselectRecordsAfterCompletion`
|
||||
- ✅ Migration برای ستونهای `row_number` و `unit_price` در shipment_items
|
||||
- ✅ Migration برای nullable کردن `name`
|
||||
- ✅ اصلاح متدهای Enum (DocNormal/DocEconomy بهجای DOC_NORMAL/DOC_ECONOMY)
|
||||
|
||||
- [ ] تست تولید PDF با بارکد
|
||||
- [ ] تست Multi-Package
|
||||
- [ ] تست Invoice Form برای PARCEL
|
||||
- [ ] تست زبان انگلیسی
|
||||
- [ ] دیپلوی روی سرور
|
||||
- [ ] تست نهایی روی سرور
|
||||
### ۳.۵.۶ — تست ترکینگ ✅
|
||||
|
||||
- ✅ اصلاح API لاراول (TrackController) با `status_label`
|
||||
- ✅ بازطراحی صفحه رهگیری وردپرس با timeline زیبا
|
||||
- ✅ باکس وضعیت با badge رنگی بر اساس status
|
||||
- ✅ grid اطلاعات کامل (۸ فیلد)
|
||||
- ✅ timeline با تاریخ، توضیحات، لوکیشن و منبع
|
||||
- ✅ بخش شرکتهای حمل
|
||||
- ✅ تست موفق با محموله چندمرحلهای
|
||||
|
||||
### ۳.۵.۷ — تست و نهاییسازی ✅
|
||||
|
||||
- ✅ تست تولید PDF با بارکد
|
||||
- ✅ تست Multi-Package
|
||||
- ✅ تست Invoice Form برای PARCEL
|
||||
- ✅ تست ترکینگ چندمرحلهای
|
||||
- ⏳ دیپلوی نهایی روی سرور (بعد از جلسه)
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -11,7 +11,6 @@ if ( ! defined( 'ABSPATH' ) ) {
|
||||
add_action('wp_footer', 'ifnex_tracking_form_scripts');
|
||||
|
||||
function ifnex_tracking_form_scripts() {
|
||||
// فقط اگر شورتکد در صفحه وجود دارد، اسکریپت را لود کن
|
||||
global $post;
|
||||
if (is_a($post, 'WP_Post') && has_shortcode($post->post_content, 'ifnex_tracking_form')) {
|
||||
?>
|
||||
@ -22,12 +21,12 @@ function ifnex_tracking_form_scripts() {
|
||||
|
||||
form.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
|
||||
var input = document.getElementById('ifnex-awb-input');
|
||||
var resultDiv = document.getElementById('ifnex-tracking-result');
|
||||
var btn = document.getElementById('ifnex-track-btn');
|
||||
var awb = input.value.trim();
|
||||
|
||||
|
||||
if (!awb) {
|
||||
resultDiv.innerHTML = '<p class="ifnex-error">لطفاً شماره بارنامه را وارد کنید.</p>';
|
||||
resultDiv.style.display = 'block';
|
||||
@ -37,21 +36,25 @@ function ifnex_tracking_form_scripts() {
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'در حال جستجو...';
|
||||
resultDiv.style.display = 'block';
|
||||
resultDiv.innerHTML = '<p>در حال جستجو...</p>';
|
||||
resultDiv.innerHTML = '<p>⏳ در حال جستجو...</p>';
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', '<?php echo esc_url(admin_url('admin-ajax.php')); ?>', true);
|
||||
xhr.open('POST', '<?php echo esc_url(admin_url("admin-ajax.php")); ?>', true);
|
||||
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
|
||||
xhr.onload = function() {
|
||||
btn.disabled = false;
|
||||
btn.textContent = '<?php echo esc_js(__('رهگیری', 'ifnex-bridge')); ?>';
|
||||
|
||||
btn.textContent = 'رهگیری';
|
||||
|
||||
if (xhr.status === 200) {
|
||||
var data = JSON.parse(xhr.responseText);
|
||||
if (data.success) {
|
||||
resultDiv.innerHTML = data.html;
|
||||
} else {
|
||||
resultDiv.innerHTML = '<p class="ifnex-error">' + data.message + '</p>';
|
||||
try {
|
||||
var data = JSON.parse(xhr.responseText);
|
||||
if (data.success) {
|
||||
resultDiv.innerHTML = data.html;
|
||||
} else {
|
||||
resultDiv.innerHTML = '<p class="ifnex-error">' + (data.message || 'خطا') + '</p>';
|
||||
}
|
||||
} catch (err) {
|
||||
resultDiv.innerHTML = '<p class="ifnex-error">پاسخ نامعتبر از سرور.</p>';
|
||||
}
|
||||
} else {
|
||||
resultDiv.innerHTML = '<p class="ifnex-error">خطا در ارتباط با سرور.</p>';
|
||||
@ -59,20 +62,19 @@ function ifnex_tracking_form_scripts() {
|
||||
};
|
||||
xhr.onerror = function() {
|
||||
btn.disabled = false;
|
||||
btn.textContent = '<?php echo esc_js(__('رهگیری', 'ifnex-bridge')); ?>';
|
||||
btn.textContent = 'رهگیری';
|
||||
resultDiv.innerHTML = '<p class="ifnex-error">خطا در ارتباط با سرور.</p>';
|
||||
};
|
||||
xhr.send('action=ifnex_track_shipment&awb=' + encodeURIComponent(awb));
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.ifnex-tracking-container {
|
||||
max-width: 600px;
|
||||
max-width: 700px;
|
||||
margin: 20px auto;
|
||||
padding: 20px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
font-family: inherit;
|
||||
}
|
||||
.ifnex-tracking-form {
|
||||
@ -84,22 +86,29 @@ function ifnex_tracking_form_scripts() {
|
||||
}
|
||||
.ifnex-form-group input {
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
padding: 12px 16px;
|
||||
border: 2px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
.ifnex-form-group input:focus {
|
||||
outline: none;
|
||||
border-color: #f37021;
|
||||
}
|
||||
.ifnex-form-group button {
|
||||
padding: 10px 20px;
|
||||
background: #0073aa;
|
||||
padding: 12px 28px;
|
||||
background: #f37021;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
.ifnex-form-group button:hover {
|
||||
background: #005a87;
|
||||
.ifnex-form-group button:hover:not(:disabled) {
|
||||
background: #e25f12;
|
||||
}
|
||||
.ifnex-form-group button:disabled {
|
||||
background: #ccc;
|
||||
@ -107,37 +116,163 @@ function ifnex_tracking_form_scripts() {
|
||||
}
|
||||
.ifnex-tracking-result {
|
||||
margin-top: 20px;
|
||||
padding: 15px;
|
||||
background: #f9f9f9;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.ifnex-error {
|
||||
color: #dc3232;
|
||||
color: #dc2626;
|
||||
background: #fef2f2;
|
||||
padding: 12px 16px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #fecaca;
|
||||
}
|
||||
.ifnex-tracking-status h3 {
|
||||
margin-top: 0;
|
||||
|
||||
/* ─── Status Box ─── */
|
||||
.ifnex-status-box {
|
||||
background: #f8fafc;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.ifnex-status-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
padding-bottom: 15px;
|
||||
border-bottom: 2px solid #f37021;
|
||||
}
|
||||
.ifnex-status-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #1e293b;
|
||||
margin: 0;
|
||||
}
|
||||
.ifnex-status-badge {
|
||||
display: inline-block;
|
||||
padding: 5px 10px;
|
||||
background: #0073aa;
|
||||
padding: 6px 16px;
|
||||
background: #f37021;
|
||||
color: white;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 10px;
|
||||
border-radius: 20px;
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.ifnex-status-badge.status-pending_payment { background: #f59e0b; }
|
||||
.ifnex-status-badge.status-processed { background: #64748b; }
|
||||
.ifnex-status-badge.status-picked_up { background: #3b82f6; }
|
||||
.ifnex-status-badge.status-in_transit { background: #8b5cf6; }
|
||||
.ifnex-status-badge.status-out_for_delivery { background: #06b6d4; }
|
||||
.ifnex-status-badge.status-delivered { background: #10b981; }
|
||||
.ifnex-status-badge.status-failed { background: #ef4444; }
|
||||
.ifnex-status-badge.status-cancelled { background: #ef4444; }
|
||||
.ifnex-status-badge.status-returned { background: #f59e0b; }
|
||||
|
||||
.ifnex-details-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
.ifnex-detail-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.ifnex-detail-label {
|
||||
font-size: 11px;
|
||||
color: #64748b;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 4px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.ifnex-detail-value {
|
||||
font-size: 14px;
|
||||
color: #1e293b;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* ─── Timeline ─── */
|
||||
.ifnex-timeline-section {
|
||||
background: white;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
}
|
||||
.ifnex-timeline-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #1e293b;
|
||||
margin: 0 0 20px 0;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
}
|
||||
.ifnex-timeline {
|
||||
margin-top: 20px;
|
||||
position: relative;
|
||||
padding-right: 30px;
|
||||
}
|
||||
.ifnex-timeline ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
.ifnex-timeline::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 10px;
|
||||
bottom: 10px;
|
||||
width: 2px;
|
||||
background: #e2e8f0;
|
||||
}
|
||||
.ifnex-timeline li {
|
||||
padding: 10px;
|
||||
border-bottom: 1px solid #eee;
|
||||
.ifnex-timeline-item {
|
||||
position: relative;
|
||||
padding-bottom: 24px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
.ifnex-timeline li:last-child {
|
||||
border-bottom: none;
|
||||
.ifnex-timeline-item:last-child {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
.ifnex-timeline-dot {
|
||||
position: absolute;
|
||||
right: -25px;
|
||||
top: 4px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 50%;
|
||||
background: #f37021;
|
||||
border: 3px solid white;
|
||||
box-shadow: 0 0 0 2px #f37021;
|
||||
}
|
||||
.ifnex-timeline-item:first-child .ifnex-timeline-dot {
|
||||
background: #10b981;
|
||||
box-shadow: 0 0 0 2px #10b981;
|
||||
}
|
||||
.ifnex-timeline-date {
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
margin-bottom: 4px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.ifnex-timeline-desc {
|
||||
font-size: 14px;
|
||||
color: #1e293b;
|
||||
margin-bottom: 4px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.ifnex-timeline-location {
|
||||
font-size: 12px;
|
||||
color: #f37021;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
.ifnex-timeline-source {
|
||||
font-size: 10px;
|
||||
color: #94a3b8;
|
||||
text-transform: uppercase;
|
||||
margin-top: 4px;
|
||||
display: inline-block;
|
||||
padding: 2px 6px;
|
||||
background: #f1f5f9;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.ifnex-empty-timeline {
|
||||
text-align: center;
|
||||
color: #94a3b8;
|
||||
padding: 30px;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
<?php
|
||||
@ -150,7 +285,7 @@ add_action('wp_ajax_nopriv_ifnex_track_shipment', 'ifnex_track_shipment_ajax');
|
||||
|
||||
function ifnex_track_shipment_ajax() {
|
||||
$awb = isset($_POST['awb']) ? sanitize_text_field($_POST['awb']) : '';
|
||||
|
||||
|
||||
if (empty($awb)) {
|
||||
wp_send_json(array('success' => false, 'message' => 'شماره بارنامه وارد نشده است.'));
|
||||
return;
|
||||
@ -164,44 +299,129 @@ function ifnex_track_shipment_ajax() {
|
||||
return;
|
||||
}
|
||||
|
||||
// پشتیبانی از هر دو فرمت خروجی API (data.shipment یا مستقیم)
|
||||
$shipment = $result['data']['shipment'] ?? $result;
|
||||
$events = $result['data']['tracking_events'] ?? $result['timeline'] ?? array();
|
||||
$carriers = $result['data']['carrier_mappings'] ?? $result['carrier_mappings'] ?? array();
|
||||
|
||||
// ساخت HTML نتیجه
|
||||
ob_start();
|
||||
?>
|
||||
<div class="ifnex-tracking-status">
|
||||
<h3>وضعیت مرسوله <?php echo esc_html($result['awb_no'] ?? ''); ?></h3>
|
||||
<div class="ifnex-status-badge">
|
||||
<?php echo esc_html($result['status'] ?? 'نامشخص'); ?>
|
||||
<div class="ifnex-status-box">
|
||||
<div class="ifnex-status-header">
|
||||
<h3 class="ifnex-status-title">📦 مرسوله <?php echo esc_html($shipment['awb_no'] ?? ''); ?></h3>
|
||||
<?php
|
||||
$status = $shipment['status'] ?? $shipment['status_label'] ?? 'unknown';
|
||||
$statusLabel = $shipment['status_label'] ?? ucwords(str_replace('_', ' ', $status));
|
||||
?>
|
||||
<span class="ifnex-status-badge status-<?php echo esc_attr($status); ?>">
|
||||
<?php echo esc_html($statusLabel); ?>
|
||||
</span>
|
||||
</div>
|
||||
<div class="ifnex-details">
|
||||
<p><strong>نوع:</strong> <?php echo esc_html($result['type'] ?? ''); ?></p>
|
||||
<p><strong>جهت:</strong> <?php echo esc_html($result['direction'] ?? ''); ?></p>
|
||||
<?php if (!empty($result['sender']['name'])): ?>
|
||||
<p><strong>فرستنده:</strong> <?php echo esc_html($result['sender']['name']); ?></p>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($result['receiver']['name'])): ?>
|
||||
<p><strong>گیرنده:</strong> <?php echo esc_html($result['receiver']['name']); ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php if (!empty($result['timeline']) && is_array($result['timeline'])): ?>
|
||||
<div class="ifnex-timeline">
|
||||
<h4>تاریخچه رهگیری</h4>
|
||||
<ul>
|
||||
<?php foreach (array_reverse($result['timeline']) as $event): ?>
|
||||
<li>
|
||||
<strong><?php echo esc_html($event['event_date'] ?? ''); ?></strong>
|
||||
<span><?php echo esc_html($event['event_description'] ?? ''); ?></span>
|
||||
<?php if (!empty($event['location'])): ?>
|
||||
<small><?php echo esc_html($event['location']); ?></small>
|
||||
<?php endif; ?>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
|
||||
<div class="ifnex-details-grid">
|
||||
<div class="ifnex-detail-item">
|
||||
<span class="ifnex-detail-label">نوع</span>
|
||||
<span class="ifnex-detail-value"><?php echo esc_html($shipment['type'] ?? '—'); ?></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="ifnex-detail-item">
|
||||
<span class="ifnex-detail-label">جهت</span>
|
||||
<span class="ifnex-detail-value"><?php echo esc_html(ucfirst($shipment['direction'] ?? '—')); ?></span>
|
||||
</div>
|
||||
<div class="ifnex-detail-item">
|
||||
<span class="ifnex-detail-label">فرستنده</span>
|
||||
<span class="ifnex-detail-value"><?php echo esc_html($shipment['sender_name'] ?? $shipment['sender']['name'] ?? '—'); ?></span>
|
||||
</div>
|
||||
<div class="ifnex-detail-item">
|
||||
<span class="ifnex-detail-label">گیرنده</span>
|
||||
<span class="ifnex-detail-value"><?php echo esc_html($shipment['receiver_name'] ?? $shipment['receiver']['name'] ?? '—'); ?></span>
|
||||
</div>
|
||||
<div class="ifnex-detail-item">
|
||||
<span class="ifnex-detail-label">مبدأ</span>
|
||||
<span class="ifnex-detail-value"><?php echo esc_html($shipment['from_country'] ?? '—'); ?></span>
|
||||
</div>
|
||||
<div class="ifnex-detail-item">
|
||||
<span class="ifnex-detail-label">مقصد</span>
|
||||
<span class="ifnex-detail-value"><?php echo esc_html($shipment['to_country'] ?? '—'); ?></span>
|
||||
</div>
|
||||
<div class="ifnex-detail-item">
|
||||
<span class="ifnex-detail-label">وزن</span>
|
||||
<span class="ifnex-detail-value"><?php echo esc_html($shipment['weight'] ?? '—'); ?> kg</span>
|
||||
</div>
|
||||
<div class="ifnex-detail-item">
|
||||
<span class="ifnex-detail-label">تاریخ ثبت</span>
|
||||
<span class="ifnex-detail-value"><?php echo esc_html(date('Y/m/d', strtotime($shipment['created_at'] ?? 'now'))); ?></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($events) && is_array($events)): ?>
|
||||
<div class="ifnex-timeline-section">
|
||||
<h4 class="ifnex-timeline-title">🚚 تاریخچه رهگیری</h4>
|
||||
<div class="ifnex-timeline">
|
||||
<?php
|
||||
// مرتبسازی از قدیم به جدید (اولین رویداد در بالا)
|
||||
$sortedEvents = array_reverse($events);
|
||||
foreach ($sortedEvents as $event):
|
||||
$date = $event['date'] ?? $event['event_date'] ?? '';
|
||||
$time = $event['time'] ?? $event['event_time'] ?? '';
|
||||
$location = $event['location'] ?? '';
|
||||
$description = $event['description'] ?? $event['event_description'] ?? '';
|
||||
$source = $event['source'] ?? '';
|
||||
?>
|
||||
<div class="ifnex-timeline-item">
|
||||
<div class="ifnex-timeline-dot"></div>
|
||||
<div class="ifnex-timeline-date">
|
||||
<?php
|
||||
$dateStr = '';
|
||||
if ($date) {
|
||||
$dateStr = date('Y/m/d', strtotime($date));
|
||||
if ($time) $dateStr .= ' - ' . substr($time, 0, 5);
|
||||
}
|
||||
echo esc_html($dateStr);
|
||||
?>
|
||||
</div>
|
||||
<?php if ($description): ?>
|
||||
<div class="ifnex-timeline-desc"><?php echo esc_html($description); ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if ($location): ?>
|
||||
<div class="ifnex-timeline-location">📍 <?php echo esc_html($location); ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if ($source): ?>
|
||||
<span class="ifnex-timeline-source"><?php echo esc_html($source); ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="ifnex-timeline-section">
|
||||
<h4 class="ifnex-timeline-title">🚚 تاریخچه رهگیری</h4>
|
||||
<div class="ifnex-empty-timeline">
|
||||
هنوز رویداد ترکینگی ثبت نشده است.
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($carriers) && is_array($carriers)): ?>
|
||||
<div class="ifnex-timeline-section" style="margin-top: 16px;">
|
||||
<h4 class="ifnex-timeline-title">🏢 شرکتهای حمل</h4>
|
||||
<?php foreach ($carriers as $carrier): ?>
|
||||
<div style="padding: 10px; background: #f8fafc; border-radius: 8px; margin-bottom: 8px;">
|
||||
<strong><?php echo esc_html($carrier['carrier_code'] ?? '—'); ?></strong>
|
||||
<?php if (!empty($carrier['carrier_tracking_number'])): ?>
|
||||
| کد رهگیری: <?php echo esc_html($carrier['carrier_tracking_number']); ?>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($carrier['delivery_status'])): ?>
|
||||
| وضعیت: <?php echo esc_html($carrier['delivery_status']); ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php
|
||||
$html = ob_get_clean();
|
||||
|
||||
|
||||
wp_send_json(array('success' => true, 'html' => $html));
|
||||
}
|
||||
|
||||
@ -253,47 +473,8 @@ function ifnex_get_transactions_ajax() {
|
||||
return;
|
||||
}
|
||||
|
||||
// ساخت HTML لیست تراکنشها
|
||||
if (empty($transactions['transactions'])) {
|
||||
wp_send_json(array(
|
||||
'success' => true,
|
||||
'html' => '<p>هنوز تراکنشی ثبت نشده است.</p>',
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
<div class="ifnex-transactions-list">
|
||||
<?php foreach ($transactions['transactions'] as $transaction): ?>
|
||||
<div class="ifnex-transaction-item">
|
||||
<div class="ifnex-transaction-header">
|
||||
<span class="ifnex-transaction-type">
|
||||
<?php echo esc_html($transaction['type']); ?>
|
||||
</span>
|
||||
<span class="ifnex-transaction-amount <?php echo $transaction['amount'] >= 0 ? 'positive' : 'negative'; ?>">
|
||||
<?php echo $transaction['amount'] >= 0 ? '+' : ''; ?>
|
||||
<?php echo number_format($transaction['amount']); ?> ریال
|
||||
</span>
|
||||
</div>
|
||||
<div class="ifnex-transaction-details">
|
||||
<p class="ifnex-transaction-description"><?php echo esc_html($transaction['description']); ?></p>
|
||||
<div class="ifnex-transaction-meta">
|
||||
<span class="ifnex-transaction-status"><?php echo esc_html($transaction['status']); ?></span>
|
||||
<span class="ifnex-transaction-date"><?php echo esc_html($transaction['created_at_jalali']); ?></span>
|
||||
</div>
|
||||
<?php if (!empty($transaction['reference_id'])): ?>
|
||||
<p class="ifnex-transaction-ref">کد پیگیری: <?php echo esc_html($transaction['reference_id']); ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php
|
||||
$html = ob_get_clean();
|
||||
|
||||
wp_send_json(array(
|
||||
'success' => true,
|
||||
'html' => $html,
|
||||
'transactions' => $transactions['transactions'] ?? array(),
|
||||
));
|
||||
}
|
||||
|
||||
@ -22,7 +22,7 @@
|
||||
|
||||
## 🎯 نمای کلی
|
||||
|
||||
این پوشه شامل **هسته مرکزی سیستم IFNEX** است که شامل چهار بخش اصلی میشود:
|
||||
این پوشه شامل **هسته مرکزی سیستم IFNEX** است که شامل پنج بخش اصلی میشود:
|
||||
|
||||
### ۱. REST API کامل
|
||||
ارتباط با WordPress از طریق Sanctum Token + Bridge Auth (بدون رمز عبور) — ۳۰+ endpoint برای تمام عملیات مشتری (سفارش، پرداخت، کیف پول، نوتیفیکیشن، رهگیری).
|
||||
@ -36,6 +36,9 @@
|
||||
### ۴. سیستم مالی و سندسازی
|
||||
کیف پول دیجیتال، درگاه پرداخت Zarinpal، تولید خودکار PDF (AWB, Invoice, Label) با بارکد استاندارد و سیستم نوتیفیکیشن دیتابیس.
|
||||
|
||||
### ۵. Multi-Package و Invoice (فاز ۳.۵)
|
||||
پشتیبانی از چند بسته در یک سفارش، فرم اقلام گمرکی (Invoice) برای محمولههای PARCEL، و محاسبه خودکار وزن حجمی از ابعاد.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 نصب و راهاندازی سریع
|
||||
@ -298,11 +301,11 @@ curl -X POST http://localhost:8000/api/v1/customer/orders \
|
||||
| Model | جدول | توضیح | فاز |
|
||||
|-------|------|--------|-----|
|
||||
| Country | countries | ۲۳۳ کشور با ۴ زون + calling_code | ۰ |
|
||||
| Shipment | shipments | مرسولهها (مدل مرکزی) | ۰ |
|
||||
| ShipmentItem | shipment_items | اقلام گمرکی | ۰ |
|
||||
| ShipmentPackage | shipment_packages | بستههای چندگانه | ۲ |
|
||||
| Shipment | shipments | مرسولهها (مدل مرکزی) | ۰+۲+۳.۵ |
|
||||
| ShipmentItem | shipment_items | اقلام گمرکی (Invoice) | ۰+۳.۵ |
|
||||
| ShipmentPackage | shipment_packages | بستههای چندگانه (Multi-Package) | ۳.۵ |
|
||||
| ShipmentCarrierMapping | shipment_carrier_mappings | نگاشت شرکتهای حمل | ۰ |
|
||||
| ShipmentTrackingEvent | shipment_tracking_events | رویدادهای ترکینگ | ۰ |
|
||||
| ShipmentTrackingEvent | shipment_tracking_events | رویدادهای ترکینگ | ۰+۲ |
|
||||
| ShipmentStatusHistory | shipment_status_histories | تاریخچه تغییرات وضعیت | ۲ |
|
||||
| ShippingRate | shipping_rates | تعرفههای حمل | ۰ |
|
||||
| SystemSetting | system_settings | تنظیمات key-value | ۰ |
|
||||
|
||||
@ -514,7 +514,7 @@ class ShipmentResource extends Resource
|
||||
->success()
|
||||
->send();
|
||||
})
|
||||
->deselectAfterCompletion(),
|
||||
->deselectRecordsAfterCompletion(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
@ -2,57 +2,73 @@
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Shipment;
|
||||
use App\Services\TrackingService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class TrackController
|
||||
/**
|
||||
* API ترکینگ مرسولات — فاز ۰
|
||||
*
|
||||
* GET /api/v1/track/{awb_no}
|
||||
*
|
||||
* خروجی: تایملاین کامل رویدادهای ترکینگ + اطلاعات پایه مرسوله + نگاشتهای شرکت حمل
|
||||
*/
|
||||
class TrackController extends Controller
|
||||
{
|
||||
public function __construct(protected TrackingService $tracking) {}
|
||||
|
||||
/**
|
||||
* دریافت تایملاین ترکینگ یک مرسوله
|
||||
*/
|
||||
public function show(string $awb_no): JsonResponse
|
||||
{
|
||||
$shipment = Shipment::where('awb_no', $awb_no)->firstOrFail();
|
||||
$shipment = Shipment::where('awb_no', $awb_no)
|
||||
->with([
|
||||
'fromCountry:id,name,iso_code',
|
||||
'toCountry:id,name,iso_code',
|
||||
'carrierMappings:id,shipment_id,carrier_code,carrier_tracking_number,booking_number,direction,delivery_status,notes',
|
||||
'trackingEvents' => fn ($query) => $query->orderBy('event_date', 'desc')->orderBy('event_time', 'desc'),
|
||||
])
|
||||
->first();
|
||||
|
||||
$events = $shipment->trackingEvents()
|
||||
->orderByDesc('event_date')
|
||||
->orderByDesc('event_time')
|
||||
->get();
|
||||
|
||||
$mappings = $shipment->carrierMappings;
|
||||
if (!$shipment) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'مرسولهای با این کد یافت نشد',
|
||||
'awb_no' => $awb_no,
|
||||
], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'awb_no' => $shipment->awb_no,
|
||||
'direction' => $shipment->direction->value,
|
||||
'type' => $shipment->type->value,
|
||||
'status' => $shipment->status->value,
|
||||
'sender' => [
|
||||
'name' => $shipment->sender_name,
|
||||
'company' => $shipment->sender_company,
|
||||
'phone' => $shipment->sender_phone,
|
||||
'city' => $shipment->sender_city,
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'shipment' => [
|
||||
'awb_no' => $shipment->awb_no,
|
||||
'direction' => $shipment->direction?->value,
|
||||
'type' => $shipment->type?->value,
|
||||
'status' => $shipment->status?->value,
|
||||
'status_label' => $shipment->status?->label(),
|
||||
'from_country' => $shipment->fromCountry?->name,
|
||||
'to_country' => $shipment->toCountry?->name,
|
||||
'sender_name' => $shipment->sender_name,
|
||||
'receiver_name' => $shipment->receiver_name,
|
||||
'weight' => $shipment->weight,
|
||||
'created_at' => $shipment->created_at?->toIso8601String(),
|
||||
],
|
||||
'carrier_mappings' => $shipment->carrierMappings->map(fn ($mapping) => [
|
||||
'carrier_code' => $mapping->carrier_code,
|
||||
'carrier_tracking_number' => $mapping->carrier_tracking_number,
|
||||
'booking_number' => $mapping->booking_number,
|
||||
'delivery_status' => $mapping->delivery_status,
|
||||
'notes' => $mapping->notes,
|
||||
]),
|
||||
'tracking_events' => $shipment->trackingEvents->map(fn ($event) => [
|
||||
'date' => $event->event_date?->toDateString(),
|
||||
'time' => $event->event_time ? ($event->event_time instanceof \Carbon\Carbon ? $event->event_time->format('H:i') : $event->event_time) : null,
|
||||
'location' => $event->location,
|
||||
'description' => $event->event_description,
|
||||
'status' => $event->delivery_status,
|
||||
'source' => $event->source,
|
||||
]),
|
||||
],
|
||||
'receiver' => [
|
||||
'name' => $shipment->receiver_name,
|
||||
'company' => $shipment->receiver_company,
|
||||
'phone' => $shipment->receiver_phone,
|
||||
'city' => $shipment->receiver_city,
|
||||
],
|
||||
'carrier_mappings' => $mappings->map(fn ($m) => [
|
||||
'carrier_code' => $m->carrier_code->value,
|
||||
'tracking_number' => $m->carrier_tracking_number,
|
||||
'booking_number' => $m->booking_number,
|
||||
'delivery_status' => $m->delivery_status?->value,
|
||||
'notes' => $m->notes,
|
||||
]),
|
||||
'timeline' => $events->map(fn ($e) => [
|
||||
'date' => $e->event_date->format('Y-m-d'),
|
||||
'time' => $e->event_time->format('H:i'),
|
||||
'location' => $e->location,
|
||||
'description' => $e->event_description,
|
||||
'delivery_status' => $e->delivery_status?->value,
|
||||
'source' => $e->source->value,
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -61,6 +61,9 @@ class Shipment extends Model
|
||||
'weight' => 'decimal:3',
|
||||
'volumetric_weight' => 'decimal:3',
|
||||
'chargeable_weight' => 'decimal:3',
|
||||
'status' => \App\Enums\ShipmentStatus::class,
|
||||
'direction' => \App\Enums\ShipmentDirection::class,
|
||||
'type' => \App\Enums\ShipmentType::class,
|
||||
];
|
||||
|
||||
// === Relationships ===
|
||||
|
||||
18
README.md
18
README.md
@ -179,11 +179,15 @@ IFNEX-Logistics/
|
||||
- اصلاح متدهای Enum (isPaid, canBeCancelledByCustomer)
|
||||
- میدلور API برای بازگرداندن JSON 401 به جای redirect
|
||||
|
||||
### فاز ۳.۵ — اصلاحات اساسی 🔄 (در دست اقدام)
|
||||
- Multi-Package در فرم سفارش مشتری (چند بسته در یک سفارش)
|
||||
- فرم Invoice برای محمولههای PARCEL (اقلام گمرکی)
|
||||
- پشتیبانی کامل چندزبانه (i18n) با Polylang
|
||||
- تست و نهاییسازی
|
||||
### فاز ۳.۵ — اصلاحات اساسی ✅ (تکمیل شده - 90%)
|
||||
- **Multi-Package** در فرم سفارش مشتری (چند بسته در یک سفارش)
|
||||
- **فرم Invoice** برای محمولههای PARCEL (اقلام گمرکی با HS Code)
|
||||
- **بازطراحی PDFها** (AWB, Invoice, Label) با بارکد مطابق نمونه کارفرما
|
||||
- **تست ترکینگ** کامل با timeline زیبا و چندمرحلهای
|
||||
- محاسبه خودکار وزن حجمی از ابعاد (فرمول: L×W×H/5000)
|
||||
- ساختار ۵ مرحلهای فرم سفارش (مسیر → تماس → اقلام → تخفیف → تأیید)
|
||||
- اصلاحات فنی (Enum casts, migration, model)
|
||||
- پشتیبانی چندزبانه (i18n) — به زمان دیپلوی منتقل شد
|
||||
|
||||
### فاز ۴ (آینده) — سیستم نمایندگی 📋
|
||||
- مدل نمایندگی کامل با کمیسیون و پنل جداگانه
|
||||
@ -334,9 +338,7 @@ php artisan serve
|
||||
|
||||
## 🚀 مراحل بعدی
|
||||
|
||||
### فاز ۳.۵ (فوری — قبل از ارائه به کارفرما)
|
||||
- Multi-Package در فرم سفارش مشتری
|
||||
- فرم Invoice برای محمولههای PARCEL
|
||||
### فاز ۳.۵ — باقیمانده (زمان دیپلوی)
|
||||
- پشتیبانی کامل چندزبانه (i18n) با Polylang
|
||||
|
||||
### فاز ۴ (آینده)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user