# 📊 EXCEL_ANALYSIS.md — تحلیل کامل فایل‌های اکسل عملیاتی > **هدف:** مرجع کامل برای هر توسعه‌دهنده‌ای که با داده‌های تاریخی IFNEX کار می‌کند > **فایل‌های تحلیل‌شده:** دو فایل اکسل آپلودشده توسط مشتری در جلسه اولیه > **تاریخ تحلیل:** August 2026 > **وضعیت:** کامل — برای مهاجرت داده و طراحی اسکیمای دیتابیس استفاده شود --- ## 📁 فهرست فایل‌های تحلیل‌شده ### فایل ۱: `4_5989927490271846355.xlsx` (فایل اصلی عملیاتی) این فایل قلب کسب‌وکار IFNEX است. شامل ۱۵ شیت است که تمام منطق کسب‌وکار، داده‌های تاریخی و قالب‌های خروجی را در خود جای داده. **شیت‌ها:** 1. `Start` — خالی (صفحه شروع) 2. `Form` — فرم ثبت یک مرسوله (۳۴ ردیف، ۱۱۵ ستون) 3. `List` — لیست کامل مرسولات تاریخی (**۳۹۵۰ ردیف**، ۱۰۲ ستون) 4. `COUNTRIES` — جدول ۲۳۳ کشور با زون‌ها 5. `AWB` — قالب بارنامه (Air Waybill) 6. `INVOICE + label` — قالب فاکتور 7. `label` — قالب لیبل 8. `Import Rate` — جدول نرخ‌های واردات (۳۸۵ ردیف) 9. `Export Rate` — جدول نرخ‌های صادرات (۳۸۵ ردیف) 10. `Zone` — جدول زون‌بندی کشورها (۲ بخش: PARCEL و DOCUMENT) 11. `DocNor` — جدول قیمت Document Normal 12. `Parcel` — جدول قیمت Parcel 13. `DocEco` — جدول قیمت Document Economy 14. `Assumptions` — تنظیمات (VAT، Packing، روزهای هفته، شهرها) 15. `DATES` — تقویم میلادی به جلالی (۷۳۲ ردیف) ### فایل ۲: `Data entry 2026-06-28.xlsx` (فایل ترکینگ دستی) این فایل به‌صورت روزانه توسط اپراتورها پر می‌شود و شامل داده‌های ترکینگ مرسولات است. در فاز ₀، این فایل باید با پنل Filament جایگزین شود. **شیت‌ها:** 1. `Sheet1` — لیست رویدادهای ترکینگ (۳۱۱ ردیف) 2. `Refrence` — نگاشت کد AWB به کد ترکینگ خارجی (۱۵ ردیف) 3. `Paste` — داده خام کپی‌شده از وب‌سایت‌های DHL/FedEx (۴۰۴ ردیف) 4. `copy` — نسخه‌ی تمیزشده Sheet1 (۴۱۵ ردیف) 5. `Delivered` — لیست مرسولات تحویل‌شده (۹۴ ردیف) 6. `test` — آزمایش‌های اپراتور (۳۹۶ ردیف) --- ## 🗂️ تحلیل شیت به شیت — فایل اصلی ### شیت ۱: `Form` — فرم ثبت یک مرسوله **ساختار:** ۳۴ ردیف × ۱۱۵ ستون (فرم افقی، نه جدولی) **محتوای کلیدی:** - اطلاعات مسیر (Route Information): HAWB No.، Date، Forwarder، From، To، Zone، Service، Type - اطلاعات بسته (Shipment): Content، Weight، Volumetric Weight، Chargable Weight، Dimensions (W×L×H) - اطلاعات فرستنده (Shipper): Company Name، Contact، Telephone، Email، Address، City، Zip، ID Number - اطلاعات گیرنده (Receiver): همان فیلدها - اطلاعات مالی (Payment): Shipping Price، Extra Service، Domestic Pickup، Packing Cost، Domestic Delivery، Warehousing Cost، Discount، Total Fee، Cash on Delivery - اقلام گمرکی (۹ ردیف): No.، Description، H.S. Code، Quantity، Unit Price، Total in USD - تنظیمات فرمول: Percent (ضریب سود)، نرخ ارز **نکته مهم:** این شیت نشان می‌دهد **۹ ردیف کالای گمرکی** در هر مرسوله قابل ثبت است. این باید در فرم ثبت سفارش آنلاین (فاز ۱) لحاظ شود. **نمونه داده واقعی:** ``` Date: 2026-07-31 Forwarder: (خالی) From: Iran (IR) To: USA (US) Zone: 3 Service: Outbound Type: NON DOC Weight: 0 kg, Volumetric: 0 kg Content: ITEM BEING SENT AS A GIFT N... Status: Processed ``` **نگاشت به دیتابیس:** این شیت اساس طراحی جدول `shipments` و `shipment_items` در فاز ۱ است. --- ### شیت ۲: `List` — لیست کامل مرسولات تاریخی ⭐ حیاتی برای مهاجرت **ساختار:** ۳۹۵۰ ردیف × ۱۰۲ ستون **توضیح:** این شیت، جدول اصلی داده‌های تاریخی IFNEX است. هر ردیف یک مرسوله از سال ۲۰۲۰ تا الان. **این داده‌ها باید در فاز ₀ به جدول `shipments` مهاجرت داده شوند.** **ستون‌های کلیدی (ردیف ۱ و ۲ سرتیتر هستند):** | # | ستون | نوع | مثال | نگاشت به DB | |---|------|-----|------|-------------| | ۱ | HAWB No. | String | 980100010 | `shipments.awb_no` | | ۲ | Date | DateTime | 2020-05-02 | `shipments.created_at` | | ۳ | Forwarder | String | DHL / FedEx / 0 | `shipment_carrier_mappings.carrier_code` | | ۴ | From | String | Iran (IR) | `shipments.from_country_id` | | ۵ | To | String | Germany | `shipments.to_country_id` | | ۶ | Zone | Integer | 3 | (محاسبه می‌شود — ذخیره نمی‌شود) | | ۷ | Service | Enum | Outbound / Inbound | `shipments.direction` | | ۸ | Type | Enum | DocNor / NON DOC | `shipments.type` (تبدیل به DOC_NORMAL/DOC_ECONOMY/PARCEL) | | ۹ | Weight | Decimal | 0.5 | `shipments.weight` | | ۱۰ | Volumetric W. | Decimal | 0 | `shipments.volumetric_weight` | | ۱۱ | Value | Decimal | 0 | (فاز ۱ — فیلد ارزش محموله) | | ۱۲ | Content | String | EDUCATINAL DOCUEMTS | `shipments.content_description` | | ۱۳ | Chargable Weight | Decimal | 0.5 | `shipments.chargeable_weight` | | ۱۴-۱۶ | WIDHTH/LENGTH/HEIGHT | Integer | 25, 15, 3 | `shipments.dimensions` (به‌صورت JSON یا فیلد جداگانه) | | ۱۷ | Third party | Boolean | 0/1 | (فاز ۱) | | ۱۸-۲۴ | Sender fields | String | (متغیر) | `shipments.sender_*` | | ۲۵-۳۲ | Receiver fields | String | (متغیر) | `shipments.receiver_*` | | ۳۳-۴۰ | Price fields | Decimal | (متغیر) | `shipments.shipping_price`، `extra_service`، `packing_cost`، `discount`، `total_fee` | | ۴۱ | REASON FOR EXPORT | String | ITEM BEING SENT AS A SAMPLE... | `shipments.reason_for_export` | | ۴۲-۸۶ | ۹ ردیف کالای گمرکی | String | (متغیر) | `shipment_items` (۹ ردیف) | | ۸۷ | TOTAL INVOICE AMOUNT IN USD | Decimal | 114 | `shipments.invoice_total_usd` | | ۸۸ | Last State | String | Processed | `shipments.status` (تبدیل شود) | | ۸۹ | Last Load | DateTime | 2025-05-26 | (metadata) | | ۹۰ | Net Dirham | Decimal | 160.69 | `shipments.net_dirham` | | ۹۱ | Net Rial | Decimal | 73113385.32 | `shipments.net_rial` | **نمونه رکورد کامل (ردیف ۴ — 980100011):** ``` AWB: 980100011 Date: 2026-02-26 Forwarder: 0 From: Iran (IR) To: China (CN) Zone: 3 Service: Outbound Type: NON DOC Weight: 0.1 kg, Volumetric: 0.225 kg Value: 60 USD Content: Electronics PCB Board Chargeable Weight: 0.5 Dimensions: 25×15×3 cm Sender: Akbar Salmanizadeh, +989132027178, Isfahan, IR, ID: 1283855623 Receiver: Chinapcbone Technology LTD, Ms Bindy Zhang, +8615814401212, SHENZHEN, CN, ZIP: 518103 Items: 1. IC LT1668, HS: 8542390001, Qty: 104, Unit: $1.1, Total: $114 Status: Processed Net Dirham: 160.69 Net Rial: 73,113,385.32 ``` **نکات مهاجرت:** - ردیف ۱ و ۲ سرتیتر هستند — ردیف ۳ به بعد داده واقعی - ردیف‌های خالی زیاد است — اسکریپت باید آنها را فیلتر کند - نام کشورها به فرمت `Iran (IR)` است — باید به `iso_code` تبدیل شود - فیلد `Type` در اکسل شامل مقادیر متنوع است: `DocNor`، `NON DOC`، `Outbound`، `Inbound` — نیاز به استانداردسازی به enum سه‌حالته - فیلد `Service` در اکسل با فیلد `direction` در DB یکی است (Outbound=export, Inbound=import) - تاریخ‌ها میلادی هستند (نه جلالی) — خوب، نیازی به تبدیل نیست --- ### شیت ۳: `COUNTRIES` — جدول کشورها **ساختار:** ۲۳۵ ردیف × ۵ ستون **ستون‌ها:** 1. `COUNTRIES` — نام کشور با کد ISO در پرانتز، مثلاً `Afghanistan (AF)` 2. `EXPORT ZONES` — زون صادرات (عدد ۱ تا ۱۰) 3. `IMPORT ZONES` — زون واردات (عدد ۱ تا ۱۰) 4. `Service` — فقط چند ردیف اول پر است (مثلاً `Inbound`، `Outbound`، `Visa Pick Up`) — به‌نظر می‌رسد دستی وارد شده، نادیده بگیر 5. `Content` — فقط چند ردیف اول پر است (`DOC`، `NON DOC`) — نادیده بگیر **نکته مهم:** این شیت، **منبع نهایی زون‌بندی نیست**. شیت `Zone` (شیت ۱۰) منبع دقیق‌تری است چون زون‌های مجزا برای PARCEL و DOCUMENT دارد. اما این شیت (`COUNTRIES`) برای تأیید تعداد کشورها (۲۳۳ کشور واقعی، با چند مورد تکراری) استفاده می‌شود. **نمونه داده:** ``` Afghanistan (AF) | 7 | 8 Albania (AL) | 6 | 8 Algeria (DZ) | 6 | 8 ... Iran (IR) | (خالی — ایران مبدا/مقصد داخلی است) ... Yemen (YE) | 7 | 8 ``` **نگاشت به دیتابیس:** این شیت فقط برای استخراج `name` و `iso_code` استفاده می‌شود. زون‌ها از شیت `Zone` (که دقیق‌تر است) گرفته می‌شوند. --- ### شیت ۴: `AWB` — قالب بارنامه (Air Waybill) **ساختار:** ۱۸۵۶ ردیف × ۱۵ ستون (قالب افقی، چند بارنامه در یک شیت) **محتوا:** قالب PDF بارنامه IFNEX. هر بارنامه شامل: - شماره AWB (مثلاً 980100011) - تاریخ - لوگوی IFNEX (با متن "We Deliver Value") - اطلاعات فرستنده و گیرنده (در دو بلوک) - اطلاعات بسته (وزن، ابعاد) - اطلاعات پرداخت (قیمت به IRR) - بارکد (در اکسل تصویر، در لاراول باید تولید شود) **نکته:** این قالب باید در فاز ۱ به‌صورت PDF در لاراول بازسازی شود. خروجی PDF باید **انگلیسی** باشد (مطابق اکسل اصلی). **نگاشت:** قالب PDF با Dompdf یا Snappy در لاراول — فاز ۱. --- ### شیت ۵: `INVOICE + label` — قالب فاکتور **ساختار:** ۱۸۳۵ ردیف × ۱۲ ستون **محتوا:** قالب فاکتور تجاری (Commercial Invoice) شامل: - INVOICE NO (همان AWB) - DATE - SHIPPER (نام شرکت، آدرس، تلفن، ایمیل) - CONSIGNEE (همان فیلدها برای گیرنده) - لیست اقلام (Description، HS Code، Quantity، Unit Price، Total) - TOTAL INVOICE AMOUNT IN USD **نکته:** در فاز ۱، این قالب به‌صورت PDF تولید می‌شود. فیلدهای invoice باید با فیلدهای `shipment_items` در دیتابیس منطبق باشند. --- ### شیت ۶: `label` — قالب لیبل **ساختار:** ۱۱ ردیف × ۱۳ ستون **محتوا:** قالب لیبل چاپی برای چاپگرهای حرارتی. شامل: - شماره AWB (بارکد) - وزن ناخالص (Gross Weight) - ابعاد (W×L×H) - وزن حجمی (Volumetric) - تاریخ - کشور مبدا و مقصد **نکته:** در فاز ۱، این قالب به‌صورت PDF کوچک (مثلاً ۱۰۰×۱۰۰ میلی‌متر) تولید می‌شود. مخصوص چاپگرهای حرارتی دفتر اصفهان. --- ### شیت ۷: `Import Rate` — نرخ‌های واردات **ساختار:** ۳۸۵ ردیف × ۱۱ ستون **ساختار جدول:** - ردیف ۳: عنوان "ROW TO IRAN - IMPORT RATE SCHEDULE" - ردیف ۴: زیرعنوان "DOCUMENT" - ردیف ۵: سرتیتر ستون‌ها — `Weight (kg) | Zone 1 | Zone 2 | Zone 3 | ... | Zone 10` - ردیف ۶ به بعد: قیمت پایه به **درهم (AED)** برای هر ترکیب وزن × زون - ردیف ۱۰: زیرعنوان "NON - DOCUMENT" - ردیف ۱۱: سرتیتر (تکراری) - ردیف ۱۲ به بعد: قیمت برای NON-DOC **نمونه داده (DOCUMENT):** ``` Weight | Zone 1 | Zone 2 | Zone 3 | Zone 4 | Zone 5 | Zone 6 | Zone 7 | Zone 8 | Zone 9 | Zone 10 0.5 | 149.32 | 169.67 | 214.14 | 231.90 | 242.64 | 255.16 | 273.96 | 383.02 | 85.71 | 85.71 1.0 | 197.20 | 195.61 | 253.99 | 245.99 | 295.54 | 308.56 | 329.35 | 390.16 | 92.86 | 92.86 1.5 | 243.41 | 237.62 | 301.99 | 272.61 | 348.30 | 348.30 | 409.41 | 429.65 | 100 | 100 2.0 | 280.61 | 282.51 | 317.42 | 290.62 | 384.39 | 384.39 | 494.54 | 474.51 | 114.29 | 114.29 ``` **نکته مهم:** این جدول فقط ۲ بخش دارد (DOCUMENT و NON-DOCUMENT). اما شیت‌های `DocNor`، `DocEco`، `Parcel` نشان می‌دهند که در واقع **۳ نوع سرویس** وجود دارد. تضاد وجود دارد: - شیت `Import Rate` فقط ۲ نوع دارد (DOCUMENT و NON-DOC) - شیت‌های جداگانه `DocNor`، `DocEco`، `Parcel` قیمت‌های متفاوتی نشان می‌دهند **تفسیر:** احتمالاً شیت `Import Rate` جدول قدیمی است و شیت‌های DocNor/DocEco/Parcel نسخه‌ی جدیدتر و دقیق‌تر هستند. در فاز ۱، باید این موضوع را با مشتری تأیید کرد. فعلاً در فاز ۰، فقط جدول `shipping_rates` را با فیلد `type` از نوع enum سه‌حالته (`DOC_NORMAL`, `DOC_ECONOMY`, `PARCEL`) طراحی می‌کنیم. **نگاشت به دیتابیس:** جدول `shipping_rates` در فاز ۱ (طبق `Phase0_Proposal.md` بخش ۶.۲). --- ### شیت ۸: `Export Rate` — نرخ‌های صادرات **ساختار:** مشابه شیت Import Rate — ۳۸۵ ردیف × ۱۱ ستون **عنوان:** "IRAN TO ROW - EXPORT RATE SCHEDULE" **نکته:** ساختار و تضادها دقیقاً مشابه شیت Import Rate است. --- ### شیت ۹: `Zone` — جدول زون‌بندی کشورها ⭐ منبع نهایی زون‌ها **ساختار:** ۲۳۰ ردیف × ۷ ستون (دو جدول کنار هم) **چیدمان:** - ستون ۱-۳: جدول PARCEL (شماره، کشور، زون) - ستون ۵-۷: جدول DOCUMENT (شماره، کشور، زون) **نمونه داده:** ``` PARCEL DOCUMENT No. | Country | Zone No. | Country | Zone 1 | Afghanistan | 7 1 | Afghanistan | 5 2 | Albania | 3 2 | Albania | 7 3 | Algeria | 4 3 | Algeria | 7 4 | Americam Samoa | 7 4 | Andorra | 7 5 | Andorra | 3 5 | Angola | 6 6 | Angola | 7 6 | Anguilla | 6 7 | Anguilla | 7 7 | Antigua | 6 8 | Antigua | 7 8 | Argentina | 7 9 | Argentina | 7 9 | Armenia | 7 10 | Armenia | 3 10 | Aruba | 7 11 | Aruba | 7 11 | Australia | 6 12 | Australia | 7 12 | Austria | 3 ``` **⚠️ کشف کلیدی:** همان کشور برای PARCEL و DOCUMENT زون‌های متفاوتی دارد! | کشور | PARCEL Zone | DOCUMENT Zone | تفاوت | |------|-------------|---------------|-------| | Afghanistan | 7 | 5 | ۲ | | Albania | 3 | 7 | ۴ | | Algeria | 4 | 7 | ۳ | | Armenia | 3 | 7 | ۴ | | Australia | 7 | 6 | ۱ | | Austria | 3 | 3 | ۰ | **نتیجه:** جدول `countries` باید **۴ زون مجزا** داشته باشد: - `export_zone_parcel` — زون صادرات برای پارسل - `export_zone_doc` — زون صادرات برای داکیومنت - `import_zone_parcel` — زون واردات برای پارسل - `import_zone_doc` — زون واردات برای داکیومنت این موضوع در `Phase0_Proposal.md` بخش ۶.۱ منعکس شده. هرگز به ۲ زون برگردان. **نکته:** شیت `Zone` فقط زون‌های export را دارد (از ایران به سایر کشورها). زون‌های import باید از شیت `COUNTRIES` استخراج شوند یا از مشتری درخواست شود. --- ### شیت ۱۰: `DocNor` — جدول قیمت Document Normal **ساختار:** ۷۱ ردیف × ۳ ستون **فرمت:** Long format (نه Wide) ``` Weight | Attribute | Value 0.5 | 1 | 2,810,429.42 0.5 | 2 | 3,600,277.54 0.5 | 3 | 5,104,556.32 0.5 | 4 | 5,641,878.04 0.5 | 5 | 6,108,148.95 0.5 | 6 | 7,213,877.11 0.5 | 7 | 8,289,630.71 1.0 | 1 | 4,049,673.89 ... ``` **تفسیر:** - `Weight` — وزن (۰.۵، ۱، ۱.۵، ۲، ۲.۵ کیلوگرم) - `Attribute` — شماره زون (۱ تا ۱۰) - `Value` — قیمت **به ریال ایران (IRR)** **نکته:** برخلاف شیت‌های Import/Export Rate که قیمت به **درهم** بود، اینجا قیمت به **ریال** است. این یعنی فرمول تبدیل (درهم × ضریب سود × نرخ روز درهم = ریال) در این شیت اعمال شده. **نگاشت:** این داده‌ها در فاز ۱ به جدول `shipping_rates` با `type = DOC_NORMAL` مهاجرت داده می‌شوند. اما به‌جای ذخیره ریال، باید قیمت پایه درهم را ذخیره کنیم (مطابق شیت Import/Export Rate). --- ### شیت ۱۱: `Parcel` — جدول قیمت Parcel **ساختار:** ۱۴۱ ردیف × ۳ ستون (مشابه DocNor) **نکته:** وزن‌ها تا ۵ کیلوگرم (یا بیشتر) می‌رسد — برای پارسل محدوده وزن بیشتر است. **نگاشت:** جدول `shipping_rates` با `type = PARCEL`. --- ### شیت ۱۲: `DocEco` — جدول قیمت Document Economy **ساختار:** ۷۱ ردیف × ۳ ستون (مشابه DocNor) **نگاشت:** جدول `shipping_rates` با `type = DOC_ECONOMY`. --- ### شیت ۱۳: `Assumptions` — تنظیمات **ساختار:** ۲۵۱ ردیف × ۱۷ ستون (ترکیبی از تنظیمات و جداول کمکی) **محتوای کلیدی:** - ردیف ۳-۶: تنظیمات سرویس‌ها و VAT - `DocNor` با VAT: ۰.۰۹ (۹٪) و Packing: ۱۰۰,۰۰۰ ریال - `DocEco` — خالی - `Parcel` — خالی - ردیف ۳-۹ (ستون ۸-۹): روزهای هفته (میلادی و شمسی) - ردیف ۳-۹ (ستون ۱۴-۱۵): شهرهای ایران با کد (اصفهان=۰۱، شیراز=۰۲، مشهد=۰۳، ...) - ردیف ۱۰ به بعد (ستون ۱۷): لیست کشورها (به ترتیب حروف الفبا) **⚠️ کشف کلیدی — VAT:** VAT در فایل اکسل ۹٪ است (`0.09`). این فیلد در PRD قدیمی ذکر نشده بود. در `Phase0_Proposal.md` به فیلدهای مالی جدول `shipments` اضافه شده. **⚠️ کشف کلیدی — Packing:** هزینه بسته‌بندی پیش‌فرض: ۱۰۰,۰۰۰ ریال. این فیلد هم در PRD غایب بود. **نگاشت به دیتابیس:** این مقادیر در جدول `system_settings` ذخیره می‌شوند: ```sql ('vat_rate', 0.09, 'VAT rate — 9%'), ('packing_cost_default', 100000, 'Default packing cost in IRR'), ('profit_margin', 1.25, 'Profit margin — set per company'), ('aed_to_irr', [نرخ روز], 'AED to IRR exchange rate'), ``` --- ### شیت ۱۴: `DATES` — تقویم میلادی به جلالی **ساختار:** ۷۳۲ ردیف × ۱۲ ستون **ستون‌ها:** Miladi، Jalali_1، Jalali_2، Jalali_3، myear، jyear، mmonthN، jmonthN، mmonthT، jmonthT، mnime، jnime **نکته:** این جدول روش قدیمی IFNEX برای تبدیل تاریخ بوده. در لاراول نیازی به این نیست — از پکیج `morilog/jalali` استفاده می‌شود. این شیت در مهاجرت نادیده گرفته می‌شود. --- ## 🗂️ تحلیل شیت به شیت — فایل ترکینگ دستی ### شیت ۱: `Sheet1` — لیست رویدادهای ترکینگ **ساختار:** ۳۱۱ ردیف × ۸ ستون **ستون‌ها:** 1. `AWB` — شماره بارنامه IFNEX (مثلاً 980103619) 2. `Last State` — آخرین وضعیت (مثلاً "Failed attempt") — فقط در موارد خاص پر شده 3. `Date` — تاریخ رویداد 4. `Time` — زمان رویداد 5. `State` — توضیح رویداد (مثلاً "Picked up by Naqel") 6. `Country` — لوکیشن (مثلاً DUBAI، MUSCAT - OMAN) 7. `Location` — معمولاً "." (نقطه) 8. `Order No` — شماره ترتیب رویداد **نمونه داده (بارنامه 980103619):** ``` AWB | Last State | Date | Time | State | Country | Location | Order No 980103619 | | 2026-06-20 | 06:35 | Waybill created. Shipment h... | SYSTEM | . | 9 980103619 | | 2026-06-24 | 02:56 | Picked up by Naqel | DUBAI | . | 10 980103619 | | 2026-06-24 | 03:01 | Arrived at Naqel Facility | DUBAI | . | 11 980103619 | | 2026-06-24 | 03:06 | Re-Weight | DUBAI | . | 12 980103619 | | 2026-06-24 | 07:05 | Out For Delivery with Courier | DUBAI | . | 13 980103619 | | 2026-06-24 | 07:57 | Prepared for delivery | DUBAI | . | 14 980103619 | | 2026-06-24 | 17:16 | Delivery attempted – City/A... | AJMAN | . | 15 980103619 | | 2026-06-25 | 07:27 | Out For Delivery with Courier | DUBAI | . | 16 980103619 | | 2026-06-25 | 10:34 | Prepared for delivery | DUBAI | . | 17 980103619 | Failed attempt| 2026-06-25 | 19:43 | Delivery attempted – Consig... | DUBAI | . | 18 ``` **نگاشت به دیتابیس:** این شیت مستقیماً به جدول `shipment_tracking_events` نگاشت می‌شود: - `AWB` → `shipment_id` (با lookup در `shipments`) - `Date` + `Time` → `event_date` + `event_time` - `State` → `event_description` - `Country` → `location` - `Last State` → `delivery_status` (اگر پر شده باشد) - `Order No` → نادیده (ترتیب با timestamp مشخص می‌شود) **نکته مهاجرت:** این داده‌ها می‌توانند در فاز ₀ به `shipment_tracking_events` با `source = 'manual'` مهاجرت داده شوند. --- ### شیت ۲: `Refrence` — نگاشت کد AWB به کد ترکینگ خارجی ⭐ حیاتی **ساختار:** ۱۵ ردیف × ۸ ستون **ستون‌ها:** 1. `Tracking Number` — کد ترکینگ شرکت خارجی 2. `Reference Number` — شماره AWB داخلی IFNEX 3. `Delivery` — وضعیت تحویل (مثلاً "Delivered") 4. `Booking Number` — شماره رزرو 5. `IM/EX` — شرکت حمل (DHL، Naghel، UPS، ...) 6. `To Country` — کشور مقصد 7. `Item Description` — توضیحات (گاهی شامل هزینه‌های اضافی مثل "100.00AED disposal charge") 8. `Delivery Agent` — عامل تحویل **نمونه داده:** ``` Tracking Number | Reference Number | Delivery | Booking Number | IM/EX | To Country | Item Description 2329941040 | 980103609 | | | DHL | | 408638268 | 980103612 | | | Naghel | | 8252492625 | 980103613 | | | DHL | | 7095511345 | 980103614 | | | DHL | | 1Z483Y5W0491404336 | 980103079 | 991035590| | | China | UPS, 100.00AED disposal charge ``` **⚠️ کشف کلیدی — چندین شرکت حمل:** این شیت نشان می‌دهد که شرکت‌های حمل زیر در سیستم IFNEX فعال هستند: | کد | نام شرکت | نوع | پشتیبانی API | |----|----------|-----|--------------| | DHL | DHL Express | بین‌المللی | ✅ | | FEDEX | FedEx | بین‌المللی | ✅ | | UPS | UPS | بین‌المللی | ✅ | | ARAMEX | Aramex | منطقه‌ای (خاورمیانه) | ✅ | | NAGHEL / Naqel | Naqel | منطقه‌ای (امارات) | ⚠️ محدود | | EMX | EMX | منطقه‌ای | ❌ | | APSITEX | APSITEX | داخلی ایران | ❌ | | IMPEX | IMPEX | منطقه‌ای | ❌ | **نتیجه:** فقط حدود ۵۰-۶۰٪ مرسولات در فاز ۳ از طریق API قابل ترکینگ خودکار هستند. بقیه همچنان دستی می‌مانند. این موضوع در `Phase0_Proposal.md` بخش ۷.۴ لحاظ شده. **⚠️ کشف کلیدی — هزینه‌های جانبی متغیر:** ردیف ۱۵ نشان می‌دهد که گاهی هزینه‌های اضافی در زمان تحویل از سمت شرکت حمل اعمال می‌شود (مثلاً "100.00AED disposal charge"). این فیلد باید در `shipment_carrier_mappings.notes` ذخیره شود و در محاسبه سود نهایی لحاظ گردد. **نگاشت به دیتابیس:** این شیت مستقیماً به جدول `shipment_carrier_mappings` نگاشت می‌شود: - `Tracking Number` → `carrier_tracking_number` - `Reference Number` → `shipment_id` (با lookup) - `Booking Number` → `booking_number` - `IM/EX` → `carrier_code` (تبدیل به enum) - `To Country` → (نادیده یا ذخیره در metadata) - `Item Description` → `notes` - `Delivery` → `delivery_status` --- ### شیت ۳: `Paste` — داده خام کپی‌شده **ساختار:** ۴۰۴ ردیف × ۱۱ ستون **توضیح:** این شیت داده‌های خام کپی‌شده از وب‌سایت‌های DHL/FedEx/UPS را در خود دارد. اپراتور این داده‌ها را پردازش می‌کند و به `Sheet1` و `copy` منتقل می‌کند. **ستون‌ها:** Tracking number (دو ستون)، شماره ترتیب N، اطلاعات Origin/Destination، Date، Local Time، Location، Event label، Delivery status **نکته:** این شیت برای مهاجرت نادیده گرفته می‌شود — داده‌های تمیز در `Sheet1` و `copy` هستند. --- ### شیت ۴: `copy` — نسخه تمیزشده **ساختار:** ۴۱۵ ردیف × ۹ ستون **توضیح:** نسخه‌ی پاکسازی‌شده‌ی Sheet1 با فیلدهای مرتب‌تر. ستون‌ها: 1. Tracking number 2. AWB 3. Delivery status 4. Date 5. Local Time 6. Event label 7. Location 8. (خالی) 9. Order No **نکته:** این شیت و `Sheet1` تکراری هستند. برای مهاجرت از `Sheet1` استفاده کنید چون کامل‌تر است. --- ### شیت ۵: `Delivered` — لیست مرسولات تحویل‌شده **ساختار:** ۹۴ ردیف × ۸ ستون **توضیح:** لیست مرسولاتی که با موفقیت تحویل داده شده‌اند. شامل: - Tracking Number - Reference Number (AWB) - Sl No (معمولاً "Delivered" یا شماره) - Booking Number - Company Code (مثلاً 1012، IMPEX) - To Country - Item Description - Delivery Agent **نکته:** این شیت برای آمارگیری استفاده می‌شود. در دیتابیس نیازی به آن نیست — می‌توان با query روی `shipment_tracking_events` لیست مشابه تولید کرد. --- ### شیت ۶: `test` — آزمایش‌های اپراتور **ساختار:** ۳۹۶ ردیف × ۸ ستون (با چند ساختار متفاوت در یک شیت) **توضیح:** شیت آزمایشی اپراتور. داده‌ها معتبر نیستند و برای مهاجرت نادیده گرفته می‌شوند. --- ## 🔢 فرمول قیمت‌گذاری (Pricing Formula) بر اساس شیت `Form` و `Assumptions`، فرمول کامل محاسبه قیمت نهایی: ### مراحل محاسبه #### مرحله ۱: محاسبه وزن حجمی ``` Volumetric Weight (kg) = (Width × Length × Height) / 5000 ``` - ابعاد به سانتی‌متر - تقسیم بر ۵۰۰۰ استاندارد جهانی هوایی #### مرحله ۲: تعیین وزن قابل پرداخت ``` Chargeable Weight = MAX(Actual Weight, Volumetric Weight) ``` #### مرحله ۳: استخراج زون ``` Zone = lookup(country, direction, type) ``` - اگر direction=export و type=PARCEL → از `countries.export_zone_parcel` - اگر direction=export و type=DOC_* → از `countries.export_zone_doc` - اگر direction=import و type=PARCEL → از `countries.import_zone_parcel` - اگر direction=import و type=DOC_* → از `countries.import_zone_doc` #### مرحله ۴: استخراج قیمت پایه (به درهم) ``` Base Price (AED) = shipping_rates[direction, type, chargeable_weight, zone] ``` #### مرحله ۵: اعمال ضریب سود ``` After Profit (AED) = Base Price × Profit Margin (مثلاً 1.25) ``` #### مرحله ۶: تبدیل به ریال ``` After Conversion (IRR) = After Profit (AED) × AED_to_IRR_Rate ``` #### مرحله ۷: اضافه هزینه‌های جانبی ``` Subtotal (IRR) = After Conversion + Extra Service (IRR) + Domestic Pickup (IRR) + Packing Cost (IRR) + Domestic Delivery (IRR) + Warehousing Cost (IRR) - Discount (IRR) ``` #### مرحله ۸: اعمال VAT ``` Total Fee (IRR) = Subtotal × (1 + VAT Rate) ``` - VAT Rate = 0.09 (۹٪) — از شیت Assumptions #### مرحله ۹: ذخیره خروجی‌ها ``` shipments.shipping_price = Base Price (AED) shipments.net_dirham = After Profit (AED) shipments.net_rial = Total Fee (IRR) shipments.total_fee = Total Fee (IRR) ``` ### نکته‌ی مهم: محموله‌های بالای ۳۰ کیلوگرم طبق PRD قدیمی (بخش ۳.۱): «محموله‌های بالای ۳۰ کیلوگرم مشمول نرخ‌های ویژه (Spot Rate) هستند و محاسبه آنلاین ندارند و باید توسط ادمین تأیید شوند.» این قانون باید در فرم استعلام قیمت (فاز ۱) لحاظ شود: اگر وزن > ۳۰ کیلوگرم، به‌جای محاسبه، پیام «کارشناس ما تماس می‌گیرد» نمایش داده شود. ### نمونه محاسبه واقعی (از شیت List، AWB 980100011) ``` Direction: Export (Outbound) Type: NON DOC → PARCEL From: Iran (IR) To: China (CN) Weight: 0.1 kg Volumetric Weight: (25 × 15 × 3) / 5000 = 0.225 kg Chargeable Weight: MAX(0.1, 0.225) = 0.225 kg → 0.5 (طبق اکسل) Zone: 3 (از شیت List) Items: 1. IC LT1668, HS: 8542390001, Qty: 104, Unit: $1.1, Total: $114 Invoice Total: $114 Net Dirham: 160.69 Net Rial: 73,113,385.32 محاسبه: Base Price (AED) = ? (از جدول Parcel) After Profit (AED) = Base × 1.25 Net Dirham = 160.69 → پس Base Price = 160.69 / 1.25 = 128.55 AED AED_to_IRR = 73,113,385.32 / 160.69 ≈ 455,057 IRR per AED ``` این تحلیل نشان می‌دهد که **نرخ درهم به ریال در زمان این محموله حدود ۴۵۵,۰۰۰ ریال بوده**. این نرخ باید در `system_settings` ذخیره شود و قابل به‌روزرسانی باشد. --- ## 📋 قوانین رهگیری (Tracking Logic) ### نگاشت کد IFNEX به کد خارجی مشتری IFNEX به مشتری یک کد اختصاصی می‌دهد (مثلاً `IFN-525` یا `980103619`). اما بسته ممکن است با کد دیگری (مثلاً `65986555` در DHL) ارسال شود. **در دیتابیس:** - `shipments.awb_no` = کد IFNEX (مثلاً 980103619) - `shipment_carrier_mappings.carrier_tracking_number` = کد خارجی (مثلاً 408638240) مشتری همیشه با کد IFNEX جستجو می‌کند. سیستم در پس‌زمینه: - در فاز ₀: رویدادها را به‌صورت دستی در `shipment_tracking_events` با `source = 'manual'` ذخیره می‌کند - در فاز ۳: با استفاده از کد خارجی، از API TrackingMore/17track رویدادها را دریافت و با `source = 'api_aggregator'` ذخیره می‌کند ### وضعیت‌های ممکن (Status Enum) بر اساس شیت‌های ترکینگ، این وضعیت‌ها در سیستم IFNEX وجود دارند: | وضعیت | توضیح | نگاشت به enum | |-------|-------|---------------| | Waybill created | بارنامه صادر شد | `processed` | | Shipment picked up | بسته دریافت شد | `picked_up` | | Processed at [Location] | در حال پردازش | `in_transit` | | Shipment has departed | حرکت کرد | `in_transit` | | Arrived at Sort Facility | رسید به مرکز دسته‌بندی | `in_transit` | | Re-Weight | وزن‌مجدد | `in_transit` | | Out For Delivery with Courier | در مسیر تحویل | `out_for_delivery` | | Prepared for delivery | آماده تحویل | `out_for_delivery` | | Delivery attempted | تلاش برای تحویل (ناموفق) | `failed` | | Delivered | تحویل داده شد | `delivered` | | Failed attempt | تلاش ناموفق | `failed` | | Returned | برگشت خورده | `returned` | این enum در `shipments.status` و `shipment_tracking_events.delivery_status` استفاده می‌شود. --- ## 🛠️ نکات مهاجرت داده (Migration Notes) ### اسکریپت مهاجرت — فاز ₀ این کارها باید توسط اسکریپت `HistoricalShipmentsImport` (در `04_Laravel/app/Imports/`) انجام شود: #### ۱. مهاجرت کشورها (از شیت COUNTRIES و Zone) ```php // خواندن شیت COUNTRIES برای استخراج نام و ISO // خواندن شیت Zone برای استخراج زون‌های PARCEL و DOCUMENT // درج در جدول countries با ۴ زون ``` #### ۲. مهاجرت مرسولات تاریخی (از شیت List) ```php // خواندن ردیف ۳ به بعد (ردیف ۱ و ۲ سرتیتر) // برای هر ردیف: // - استخراج iso_code از نام کشور (مثلاً "Iran (IR)" → "IR") // - تبدیل تاریخ میلادی به timestamp // - تبدیل نوع سرویس (DocNor → DOC_NORMAL، NON DOC → PARCEL، ...) // - درج در جدول shipments ``` #### ۳. مهاجرت نگاشت کدهای ترکینگ (از شیت Refrence) ```php // برای هر ردیف: // - lookup shipment_id با awb_no // - تبدیل IM/EX به carrier_code enum // - درج در جدول shipment_carrier_mappings ``` #### ۴. مهاجرت رویدادهای ترکینگ (از شیت Sheet1 — اختیاری) ```php // برای هر ردیف: // - lookup shipment_id با awb_no // - parse Date و Time // - درج در جدول shipment_tracking_events با source = 'manual' ``` ### چالش‌های مهاجرت ۱. **نام کشورها با فرمت‌های متفاوت:** - `Iran (IR)`، `IRAN`، `iran` — باید استاندارد شوند - راه‌حل: استخراج iso_code از پرانتز، یا fuzzy matching با جدول countries ۲. **فیلد Type متناقض:** - مقادیر اکسل: `DocNor`، `DocEco`، `NON DOC`، `Outbound`، `Inbound`، خالی - مقادیر DB: `DOC_NORMAL`، `DOC_ECONOMY`، `PARCEL` - راه‌حل: mapping table در اسکریپت مهاجرت ۳. **فیلد Service متناقض:** - مقادیر اکسل: `Outbound`، `Inbound`، خالی - مقادیر DB: `import`، `export` - راه‌حل: `Outbound → export`، `Inbound → import` ۴. **ردیف‌های خالی زیاد:** - در شیت List، حدود نیمی از ردیف‌ها خالی هستند - راه‌حل: `if (empty($row['awb_no'])) continue;` ۵. **تاریخ‌ها با فرمت متنوع:** - بعضی ردیف‌ها `2020-05-02`، بعضی `2020-05-02 00:00:00`، بعضی خالی - راه‌حل: Carbon::parse() با مدیریت خطا ۶. **نام شرکت‌های حمل با املای متفاوت:** - `DHL`، `dhl`، `DHL Express`، `DHL8336801664` - راه‌حل: استانداردسازی به enum ### تست مهاجرت پس از مهاجرت، این تست‌ها باید انجام شوند: ```bash # ۱. شمارش رکوردها php artisan tinker >>> Shipment::count(); # باید حدود 3950 باشد # ۲. نمونه‌گیری تصادفی 50 رکورد >>> Shipment::inRandomOrder()->take(50)->get(); # دستی با اکسل مقایسه شود # ۳. بررسی یکتایی AWB >>> Shipment::where('awb_no', '980100011')->count(); # باید 1 باشد # ۴. بررسی روابط >>> Shipment::find(1)->carrierMappings; >>> Shipment::find(1)->trackingEvents; ``` --- ## 📊 خلاصه‌ی نگاشت شیت‌ها به جداول دیتابیس | شیت اکسل | جدول DB | فاز | یادداشت | |----------|---------|-----|---------| | `COUNTRIES` | `countries` | ۰ | برای name و iso_code | | `Zone` | `countries` | ۰ | برای ۴ زون (منبع نهایی) | | `List` | `shipments` | ۰ | ۳۹۵۰ رکورد تاریخی | | `List` (۹ ردیف کالای گمرکی) | `shipment_items` | ۱ | در فاز ۱ ساخته می‌شود | | `Form` | (قالب رابط کاربری) | ۱ | فرم ثبت سفارش | | `Import Rate` | `shipping_rates` | ۱ | برای type=DOC_NORMAL/PARCEL | | `Export Rate` | `shipping_rates` | ۱ | برای type=DOC_NORMAL/PARCEL | | `DocNor` | `shipping_rates` | ۱ | برای type=DOC_NORMAL | | `DocEco` | `shipping_rates` | ۱ | برای type=DOC_ECONOMY | | `Parcel` | `shipping_rates` | ۱ | برای type=PARCEL | | `Assumptions` | `system_settings` | ۰ | VAT، Packing، Profit Margin | | `AWB` | (قالب PDF) | ۱ | تولید PDF با Dompdf | | `INVOICE + label` | (قالب PDF) | ۱ | تولید PDF | | `label` | (قالب PDF) | ۱ | تولید PDF برای چاپگر حرارتی | | `DATES` | — | — | نادیده (از morilog/jalali استفاده می‌شود) | | `Start` | — | — | نادیده (خالی) | **از فایل ترکینگ دستی:** | شیت اکسل | جدول DB | فاز | یادداشت | |----------|---------|-----|---------| | `Sheet1` | `shipment_tracking_events` | ۰ | مهاجرت رویدادهای تاریخی | | `Refrence` | `shipment_carrier_mappings` | ۰ | مهاجرت نگاشت‌ها | | `Paste` | — | — | نادیده (داده خام) | | `copy` | — | — | نادیده (تکراری با Sheet1) | | `Delivered` | — | — | نادیده (با query قابل تولید) | | `test` | — | — | نادیده (آزمایشی) | --- ## ⚠️ ریسک‌ها و هشدارهای داده‌ای ### ۱. کیفیت داده‌های تاریخی - حدود ۲۰٪ رکوردها فیلدهای کلیدی خالی دارند (مخصوصاً اطلاعات گیرنده) - تاریخ‌ها گاهی به‌صورت متن ذخیره شده‌اند (نه DateTime) - نام کشورها گاهی با غلط املایی هستند (مثلاً `Americam Samoa` به‌جای `American Samoa`) **راهکار:** اسکریپت مهاجرت باید دارای validation و reporting باشد — تعداد ردیف‌های نامعتبر را گزارش کند. ### ۲. تضاد قیمت‌ها - شیت Import/Export Rate فقط ۲ نوع دارد (DOCUMENT/NON-DOC) - شیت‌های DocNor/DocEco/Parcel قیمت‌های متفاوتی دارند (۳ نوع) **راهکار:** در فاز ۱، با مشتری تأیید کنید کدام منبع معتبر است. فعلاً در فاز ₀، جدول `shipping_rates` را با enum ۳-حالته طراحی کنید تا آماده هر سناریو باشد. ### ۳. زون‌های Import - شیت `Zone` فقط زون‌های export دارد - زون‌های import باید از شیت `COUNTRIES` استخراج شوند (اما فقط ۱ زون import دارد، نه ۲) **راهکار:** در فاز ₁، با مشتری درباره زون‌های import پارسل/داکیومنت صحبت کنید. فعلاً در فاز ₀، فیلدهای `import_zone_parcel` و `import_zone_doc` را با مقدار یکسان از `COUNTRIES` پر کنید. ### ۴. فیلدهای مالی گاهی خالی - در شیت List، بسیاری از فیلدهای مالی (`shipping_price`، `extra_service`، ...) صفر یا خالی هستند - ممکن است داده‌های مالی واقعی در سیستم حسابداری جداگانه‌ای باشد **راهکار:** در مهاجرت، فیلدهای خالی را به `null` تبدیل کنید، نه به `0`. این کمک می‌کند تمایز بین «صفر واقعی» و «داده گم‌شده» حفظ شود. --- ## 🎯 جمع‌بندی برای نمونه‌ی جدید اگر نمونه‌ی جدیدی هستی که این فایل را می‌خوانی، نکات کلیدی زیر را به خاطر بسپار: ۱. **فایل‌های اکسل دو نوعند:** فایل اصلی (`4_5989927490271846355.xlsx`) و فایل ترکینگ دستی (`Data entry 2026-06-28.xlsx`). ۲. **شیت `Zone` منبع نهایی زون‌هاست** — ۴ زون مجزا (PARCEL × DOCUMENT × Export × Import). ۳. **۳ نوع سرویس وجود دارد:** DocNor، DocEco، Parcel — نه ۲ نوع. ۴. **VAT = ۹٪** و **Packing Cost پیش‌فرض = ۱۰۰,۰۰۰ ریال** — از شیت Assumptions. ۵. **۹ ردیف کالای گمرکی** در هر مرسوله — برای فرم ثبت سفارش (فاز ₁). ۶. **۳۹۵۰ رکورد تاریفی** در شیت List — باید در فاز ₀ مهاجرت داده شوند. ۷. **۸ شرکت حمل فعال:** DHL، FedEx، UPS، Aramex، Nagel، EMX، APSITEX، IMPEX — فقط ۴ تای اول API دارند. ۸. **هزینه‌های جانبی متغیر** گاهی در فیلد description شیت Refrence ذخیره شده‌اند (مثلاً "100AED disposal charge"). ۹. **محموله‌های بالای ۳۰ کیلوگرم** مشمول نرخ ویژه (Spot Rate) هستند — محاسبه آنلاین ندارند. ۱۰. **PDFها باید انگلیسی باشند** — مطابق اکسل اصلی. اما پنل ادمین و رابط کاربری فارسی است. برای جزئیات بیشتر درباره اسکیمای دیتابیس و فرمول قیمت‌گذاری، فایل `Phase0_Proposal.md` بخش ۶ و ۷ را بخوانید. --- © 2026 VernaSoft Group. Internal use only.