حساب موعد التسليم في السلة بين بلد الشحن وبلد التسليم

تحتاج صفحة الدفع التي تعرض موعد التسليم إلى عدّ أيام الإرسال في بلد المستودع وأيام شركة الشحن في بلد الوجهة. يوفّر Narwhal الإجازات الرسمية للبلدين، ويبقى موعد الإغلاق وأيام الشحن من إعدادات المتجر.

التاريخ في السلة يُعد وفق تقويمين

يرى العميل تاريخًا واحدًا لكن بلدين يقرّرانه. يرسل المستودع في أيام عمله فقط، وتسلّم شركة الشحن في أيام عمل الوجهة فقط. عشية عيد الميلاد يوم عادي لمستودع في ألمانيا، لكنها إجازة رسمية لمشترٍ في بولندا. يسأل هذا الطلب بولندا عن أقرب يوم إغلاق بدءًا من يوم طلب نموذجي.

curl، أقرب يوم إغلاق في بلد الوجهة
curl -s -H "Authorization: Bearer $NARWHAL_API_KEY" \
  "https://api.narwhalapi.com/v1/calendars/holidays/POL/next?from_date=2026-12-21"
الاستجابة
{
  "country": "POL",
  "coverage": [
    {
      "scope": "national_public",
      "subdivision_codes": []
    }
  ],
  "holiday": {
    "date": "2026-12-24",
    "name": "Christmas Eve",
    "holiday_key": "christmas_eve",
    "local_name": "Wigilia Bożego Narodzenia",
    "scope": "national_public",
    "subdivision_codes": []
  }
}

يعطي الكائن holiday الحقول date وname بالإنجليزية وlocal_name وscope. الطلب نفسه لألمانيا يعيد عيد الميلاد في 25 ديسمبر، لذلك يأتي أول إغلاق في بولندا قبل أول إغلاق في ألمانيا بيوم. يبيّن coverage أن التقويمين يضمان الإجازات الوطنية فقط.

ابنِ الموعد من موعد الإغلاق والإرسال وأيام الشحن

لكل مدخل مصدر مختلف. يبيّن الجدول ما يأتي من المتجر وما يأتي من التقويم.

المدخلالمالكمثال
موعد إغلاق الطلباتإعداد المتجر16:00 بتوقيت المستودع
يوم الإرسالتقويم المستودعأول يوم عمل مفتوح في أيام الأسبوع عند موعد الإغلاق أو بعده
أيام الشحنعقد شركة الشحن3 أيام
يوم التسليمتقويم الوجهةيعد أيام العمل المفتوحة فقط
الإجازات الرسميةقائمة Narwhal السنويةطلب واحد لكل بلد وسنة

تنقل الدالة أدناه الطلب إلى يوم العمل التالي في المستودع إذا وصل بعد موعد الإغلاق أو في يوم مغلق. ثم تعدّ أيام الشحن في أيام عمل الوجهة. تحمّل كل بلد وسنة مرة واحدة، وتحمّل السنة التالية حين يتجاوز العدّ 1 يناير.

python، تواريخ الإرسال والتسليم
import json, os, urllib.request
from datetime import datetime, time, timedelta
from functools import cache

def get(path):
    req = urllib.request.Request("https://api.narwhalapi.com" + path,
                                 headers={"Authorization": "Bearer " + os.environ["NARWHAL_API_KEY"]})
    with urllib.request.urlopen(req, timeout=10) as r:
        return json.load(r)

@cache
def closed_days(country, year):
    return {h["date"] for h in get(f"/v1/calendars/holidays/{country}/{year}")["holidays"]}

def is_open(day, country):
    return day.weekday() < 5 and day.isoformat() not in closed_days(country, day.year)

def promise(placed, cutoff, origin, dest, transit_days):
    dispatch = placed.date()
    if placed.time() >= cutoff or not is_open(dispatch, origin):
        dispatch += timedelta(1)
        while not is_open(dispatch, origin):
            dispatch += timedelta(1)
    day, left = dispatch, transit_days
    while left:
        day += timedelta(1)
        left -= is_open(day, dest)
    return dispatch, day

CUTOFF = time(16, 0)
for placed, dest in [
    (datetime(2026, 12, 21, 14, 0), "POL"),
    (datetime(2026, 12, 21, 17, 30), "POL"),
    (datetime(2026, 12, 21, 17, 30), "DEU"),
    (datetime(2026, 12, 31, 10, 0), "POL"),
]:
    dispatch, arrive = promise(placed, CUTOFF, "DEU", dest, 3)
    print(f"{placed:%a %d %b %H:%M} to {dest}: dispatch {dispatch:%a %d %b}, delivery {arrive:%a %d %b %Y}")
الإنتاج
Mon 21 Dec 14:00 to POL: dispatch Mon 21 Dec, delivery Mon 28 Dec 2026
Mon 21 Dec 17:30 to POL: dispatch Tue 22 Dec, delivery Tue 29 Dec 2026
Mon 21 Dec 17:30 to DEU: dispatch Tue 22 Dec, delivery Mon 28 Dec 2026
Thu 31 Dec 10:00 to POL: dispatch Thu 31 Dec, delivery Thu 07 Jan 2027

طلب في الساعة 14:00 يوم الاثنين 21 ديسمبر يغادر في اليوم نفسه ويصل الاثنين 28 ديسمبر. والطلب نفسه في الساعة 17:30 يغادر الثلاثاء 22 ديسمبر ويصل الثلاثاء 29 ديسمبر، أي بعد الأول بيوم لأن عشية عيد الميلاد مغلقة في بولندا. وإذا أُرسل إلى ألمانيا بدلًا من ذلك يصل طلب 17:30 الاثنين 28 ديسمبر. أما طلب الخميس 31 ديسمبر فيعبر السنة ويتخطى 1 يناير وعيد الظهور الإلهي في بولندا يوم 6 يناير، ويصل الخميس 7 يناير 2027.

اعرض نطاقًا واحتفظ بالقاعدة في مكان واحد

تعطي شركات الشحن نطاقًا مثل 2 إلى 3 أيام. شغّل الدالة مرة لكل طرف. للطلب المذكور في الساعة 17:30، يعطي يومان من الشحن الاثنين 28 ديسمبر وثلاثة أيام الثلاثاء 29 ديسمبر، فتستطيع السلة أن تقول إن التسليم بين هذين التاريخين. يجب أن تكون قاعدة موعد الإغلاق وأيام الشحن وعطلة نهاية الأسبوع في دالة واحدة تستدعيها السلة ورسالة التأكيد وصفحة الطلب، حتى لا تعرض الثلاثة تواريخ مختلفة. حوّل وقت الطلب إلى ساعة المستودع أولًا، لأن مساء المشتري قد يكون صباح اليوم التالي في المستودع، واختبار موعد الإغلاق يقرأ وقت المستودع.

يسرد التقويم الإجازات الرسمية. ولا يعرف إغلاق مستودع أو أيام الذروة التي تفرض فيها شركة الشحن رسومًا إضافية أو إغلاقًا إقليميًا. تحمل استجابة ألمانيا النطاق الوطني فقط، لذلك لا توجد إجازات الولايات. أضف هذه التواريخ إلى المجموعة التي تقرأها الدالة.

تقويم أي بلد يُعتمد في كل خطوة؟

بلد المستودع يحدد يوم الإرسال. وبلد الوجهة يحدد أيام الشحن، لأن المرحلة الأخيرة تجري هناك. إذا مرت الطرد ببلد ثالث، فأضف أيامه إلى العدّ فقط إذا كانت شركة الشحن تحسبها.

ماذا يحدث لطلب يُقدَّم في يوم مغلق؟

يبدأ من يوم العمل التالي في المستودع. تفحص الدالة تاريخ الطلب بـis_open قبل تطبيق موعد الإغلاق، لذلك يُرسل طلب السبت أو يوم الإجازة في يوم العمل المفتوح التالي. موعد الإغلاق قاعدة للمتجر ويُقارن بالتوقيت المحلي للمستودع.

هل تستدعي صفحة الدفع API عند كل عرض للسلة؟

لا حاجة لذلك. القائمة السنوية واحدة لكل طلبات بلد وسنة، لذلك يحتفظ بها السكربت في ذاكرة مؤقتة ويكفي طلب واحد لكل السلال. حدّث الذاكرة وفق جدول يناسبك. يعرض دليل إضافة أيام العمل المسار نفسه في بلد واحد، ويعرض دليل فحص الإجازة الرسمية نقطة النهاية لتاريخ واحد في خطوة التأكيد.

هل يمكن أن يُحسب السبت يوم تسليم؟

نعم، إذا كانت شركة الشحن تسلّم يوم السبت. غيّر اختبار اليوم في is_open من day.weekday() < 5 إلى day.weekday() < 6. يبقى فحص الإجازة قائمًا، فيُتخطى السبت الذي يصادف إجازة رسمية في الوجهة. احتفظ باختبار للإرسال وآخر للتسليم إذا كان المستودع مغلقًا يوم السبت وشركة الشحن ليست كذلك.

نُشر في .

الخطوات التالية