ifnex/01_Documents/EXCEL_ANALYSIS.md
2026-08-02 05:13:12 +03:30

43 KiB
Raw Blame History

📊 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 ذخیره می‌شوند:

('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 نگاشت می‌شود:

  • AWBshipment_id (با lookup در shipments)
  • Date + Timeevent_date + event_time
  • Stateevent_description
  • Countrylocation
  • Last Statedelivery_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 Numbercarrier_tracking_number
  • Reference Numbershipment_id (با lookup)
  • Booking Numberbooking_number
  • IM/EXcarrier_code (تبدیل به enum)
  • To Country → (نادیده یا ذخیره در metadata)
  • Item Descriptionnotes
  • Deliverydelivery_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)

// خواندن شیت 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.