43 KiB
📊 EXCEL_ANALYSIS.md — تحلیل کامل فایلهای اکسل عملیاتی
هدف: مرجع کامل برای هر توسعهدهندهای که با دادههای تاریخی IFNEX کار میکند
فایلهای تحلیلشده: دو فایل اکسل آپلودشده توسط مشتری در جلسه اولیه
تاریخ تحلیل: August 2026
وضعیت: کامل — برای مهاجرت داده و طراحی اسکیمای دیتابیس استفاده شود
📁 فهرست فایلهای تحلیلشده
فایل ۱: 4_5989927490271846355.xlsx (فایل اصلی عملیاتی)
این فایل قلب کسبوکار IFNEX است. شامل ۱۵ شیت است که تمام منطق کسبوکار، دادههای تاریخی و قالبهای خروجی را در خود جای داده.
شیتها:
Start— خالی (صفحه شروع)Form— فرم ثبت یک مرسوله (۳۴ ردیف، ۱۱۵ ستون)List— لیست کامل مرسولات تاریخی (۳۹۵۰ ردیف، ۱۰۲ ستون)COUNTRIES— جدول ۲۳۳ کشور با زونهاAWB— قالب بارنامه (Air Waybill)INVOICE + label— قالب فاکتورlabel— قالب لیبلImport Rate— جدول نرخهای واردات (۳۸۵ ردیف)Export Rate— جدول نرخهای صادرات (۳۸۵ ردیف)Zone— جدول زونبندی کشورها (۲ بخش: PARCEL و DOCUMENT)DocNor— جدول قیمت Document NormalParcel— جدول قیمت ParcelDocEco— جدول قیمت Document EconomyAssumptions— تنظیمات (VAT، Packing، روزهای هفته، شهرها)DATES— تقویم میلادی به جلالی (۷۳۲ ردیف)
فایل ۲: Data entry 2026-06-28.xlsx (فایل ترکینگ دستی)
این فایل بهصورت روزانه توسط اپراتورها پر میشود و شامل دادههای ترکینگ مرسولات است. در فاز ₀، این فایل باید با پنل Filament جایگزین شود.
شیتها:
Sheet1— لیست رویدادهای ترکینگ (۳۱۱ ردیف)Refrence— نگاشت کد AWB به کد ترکینگ خارجی (۱۵ ردیف)Paste— داده خام کپیشده از وبسایتهای DHL/FedEx (۴۰۴ ردیف)copy— نسخهی تمیزشده Sheet1 (۴۱۵ ردیف)Delivered— لیست مرسولات تحویلشده (۹۴ ردیف)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 — جدول کشورها
ساختار: ۲۳۵ ردیف × ۵ ستون
ستونها:
COUNTRIES— نام کشور با کد ISO در پرانتز، مثلاًAfghanistan (AF)EXPORT ZONES— زون صادرات (عدد ۱ تا ۱۰)IMPORT ZONES— زون واردات (عدد ۱ تا ۱۰)Service— فقط چند ردیف اول پر است (مثلاًInbound،Outbound،Visa Pick Up) — بهنظر میرسد دستی وارد شده، نادیده بگیر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 ذخیره میشوند:
('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 — لیست رویدادهای ترکینگ
ساختار: ۳۱۱ ردیف × ۸ ستون
ستونها:
AWB— شماره بارنامه IFNEX (مثلاً 980103619)Last State— آخرین وضعیت (مثلاً "Failed attempt") — فقط در موارد خاص پر شدهDate— تاریخ رویدادTime— زمان رویدادState— توضیح رویداد (مثلاً "Picked up by Naqel")Country— لوکیشن (مثلاً DUBAI، MUSCAT - OMAN)Location— معمولاً "." (نقطه)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_timeState→event_descriptionCountry→locationLast State→delivery_status(اگر پر شده باشد)Order No→ نادیده (ترتیب با timestamp مشخص میشود)
نکته مهاجرت: این دادهها میتوانند در فاز ₀ به shipment_tracking_events با source = 'manual' مهاجرت داده شوند.
شیت ۲: Refrence — نگاشت کد AWB به کد ترکینگ خارجی ⭐ حیاتی
ساختار: ۱۵ ردیف × ۸ ستون
ستونها:
Tracking Number— کد ترکینگ شرکت خارجیReference Number— شماره AWB داخلی IFNEXDelivery— وضعیت تحویل (مثلاً "Delivered")Booking Number— شماره رزروIM/EX— شرکت حمل (DHL، Naghel، UPS، ...)To Country— کشور مقصدItem Description— توضیحات (گاهی شامل هزینههای اضافی مثل "100.00AED disposal charge")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_numberReference Number→shipment_id(با lookup)Booking Number→booking_numberIM/EX→carrier_code(تبدیل به enum)To Country→ (نادیده یا ذخیره در metadata)Item Description→notesDelivery→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 با فیلدهای مرتبتر. ستونها:
- Tracking number
- AWB
- Delivery status
- Date
- Local Time
- Event label
- Location
- (خالی)
- 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)
// خواندن شیت COUNTRIES برای استخراج نام و ISO
// خواندن شیت Zone برای استخراج زونهای PARCEL و DOCUMENT
// درج در جدول countries با ۴ زون
۲. مهاجرت مرسولات تاریخی (از شیت List)
// خواندن ردیف ۳ به بعد (ردیف ۱ و ۲ سرتیتر)
// برای هر ردیف:
// - استخراج iso_code از نام کشور (مثلاً "Iran (IR)" → "IR")
// - تبدیل تاریخ میلادی به timestamp
// - تبدیل نوع سرویس (DocNor → DOC_NORMAL، NON DOC → PARCEL، ...)
// - درج در جدول shipments
۳. مهاجرت نگاشت کدهای ترکینگ (از شیت Refrence)
// برای هر ردیف:
// - lookup shipment_id با awb_no
// - تبدیل IM/EX به carrier_code enum
// - درج در جدول shipment_carrier_mappings
۴. مهاجرت رویدادهای ترکینگ (از شیت Sheet1 — اختیاری)
// برای هر ردیف:
// - 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
تست مهاجرت
پس از مهاجرت، این تستها باید انجام شوند:
# ۱. شمارش رکوردها
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.