القائمة

التوريدات والسحوبات

العروض بنمط التجهيز stocked تعيش في مستودع واميا: المستودع يعدّها، والمتجر يزوّدها بموعد مسبق ويسترجع المخزون بطلب سحب. هذه الصفحة هي جانب التكامل من هذا التدفق — Wamia Shop وتطبيق الجوال يقودان العمليات نفسها.

أي العروض

العروض التي fulfilmentMode فيها يساوي stocked هي وحدها القابلة للإعلان والتزويد والسحب. عروض العبور المباشر تبقى في مخزون المتجر وتسافر مع كل طلب؛ أي تزويد يسمّي واحدة منها يُرفض كاملاً بـ VALIDATION_FAILED مع ذكر SKU المخالفة في الرسالة.

للعروض المخزّنة، عدّ المستودع هو الحقيقة. apiDeclareStocks يقبل السطر لكنه لا يكتب شيئاً: يعود السطر ok: true مع skipped: true وskipReason: stocked_offer. اعكس apiStockedPositions.availableQty في المتجر بدلاً من ذلك.

إعلان تزويد

apiDeclareInbound يأخذ الأسطر (offerId، qty)، والمستودع (اختياري: ينطبق المستودع الافتراضي للمتجر)، وinboundMode — SELLER_DROPOFF حين يُحضر المتجر البضاعة، وWAMIA_PICKUP حين يجمعها سائق واميا مع جهة الاتصال والعنوان في pickup — وsellerReference وملاحظة. النداء مفتاحي: إعادة إرسال نفس idempotencyKey تعيد الشحنة نفسها. الجواب InboundShipmentView بـ origin: SELLER_DECLARED في الحالة DRAFT.

apiInboundShipments(origin: SELLER_DECLARED) يسرد التزويدات؛ وORDER_DRIVEN يسرد تحويلات العبور التي أنشأتها واميا من الطلبات. apiInboundShipment يعيد الأسطر والموعد ومهمة الجمع وفروقات شحنة واحدة.

حجز الموعد وتغييره

التسليم يحتاج فترة رصيف. apiInboundSlotGrid(facilityId, from, to) يعيد فترات كل يوم على نافذة لا تتجاوز 14 يوماً مع capacity وbooked وremaining؛ وapiInboundSlots(facilityId, day) هو شكل اليوم الواحد. احجز بـ apiBookInboundAppointment(shipmentId, dockId, startsAt): تنتقل الشحنة إلى SCHEDULED ويُطلق inbound.scheduled. الفترات الماضية أو خارج ساعات الرصيف أو داخل مهلة المستودع أو الممتلئة تُرفض بـ VALIDATION_FAILED.

لنقل موعد محجوز نادِ apiRescheduleInboundAppointment بالفترة الجديدة وملاحظة اختيارية؛ تُحرَّر الفترة السابقة ويُطلق inbound.rescheduled مع previousAppointmentId. النداءان بلا مفتاح ولا تعيدهما SDK أبداً: بعد انتهاء المهلة اقرأ apiInboundShipment وانظر إلى appointment قبل النداء مجدداً. يتلقى المتجر تذكيراً قبل يوم؛ الفترة الفائتة تصبح inbound.no_show وتعود الشحنة إلى DRAFT لإعادة الحجز.

السحوبات

apiRequestWithdrawal يطلب من المستودع إعادة المخزون: facilityCode، وأسطر بـ sku أو offerId مع qty، وreason وملاحظة اختياريان. النداء مفتاحي. يبدأ الطلب في REQUESTED؛ يوافق المستودع أو يرفض (withdrawal.decided؛ الموافقة تحجز الوحدات)، وتحجزون موعداً عند الرصيف بـ apiBookWithdrawalAppointment (الفترات من apiInboundSlots؛ وapiRescheduleWithdrawalAppointment تنقله؛ withdrawal.scheduled)، ويحضّره المستودع (withdrawal.ready عندما ينتظر عند الرصيف) ويسجّل التسليم (withdrawal.handed_over، مع سبب نقص لكل سطر إن كان جزئياً). يُوقَّع وصل السحب على الورق. apiWithdrawals(status) يسرد طلبات المتجر مع appointment. الكميات فوق availableQty تُرفض: الوحدات المحجوزة تعود للطلبات المفتوحة.

اعكس المراكز ولا تعلن عنها

apiStockedPositions(updatedSince, facilityCode) مقسّم بمؤشر مثل القوائم. استعلم عنه بإيقاع مزامنة مخزونك واكتب availableQty في المتجر؛ onHandQty ناقص reservedQty هو ما يمكن للواجهة بيعه. inbound.received وwithdrawal.handed_over هما لحظتا تحرّك المراكز؛ أي ويب هوك من إحدى العائلتين مُحفّز جيد لسحب تزايدي.

مثال

إعلان تزويد

GraphQL
mutation DeclareTopUp($input: DeclareInboundInput!, $idempotencyKey: String!) {
  apiDeclareInbound(input: $input, idempotencyKey: $idempotencyKey) {
    id reference status origin inboundMode facilityId facilityCode expectedUnits dueAt
  }
}
المتغيرات
{
  "idempotencyKey": "inbound:PO-77:3f9c…",
  "input": {
    "inboundMode": "SELLER_DROPOFF",
    "sellerReference": "PO-77",
    "lines": [{ "offerId": "0f6a…", "qty": 120 }]
  }
}

الفترات والحجز والتغيير

GraphQL
query Slots($facilityId: String!, $from: DateTime!, $to: DateTime!) {
  apiInboundSlotGrid(facilityId: $facilityId, from: $from, to: $to) {
    day
    slots { dockId dockCode startsAt endsAt capacity booked remaining closedReason }
  }
}

mutation Book($shipmentId: String!, $dockId: String!, $startsAt: DateTime!) {
  apiBookInboundAppointment(shipmentId: $shipmentId, dockId: $dockId, startsAt: $startsAt) {
    id status scheduledAt appointmentId
  }
}

mutation Move($shipmentId: String!, $dockId: String!, $startsAt: DateTime!, $note: String) {
  apiRescheduleInboundAppointment(shipmentId: $shipmentId, dockId: $dockId, startsAt: $startsAt, note: $note) {
    id status scheduledAt appointmentId
  }
}

طلب سحب

GraphQL
mutation Withdraw($input: ApiWithdrawalInput!, $idempotencyKey: String!) {
  apiRequestWithdrawal(input: $input, idempotencyKey: $idempotencyKey) {
    id status facilityId reason createdAt
  }
}
المتغيرات
{
  "idempotencyKey": "withdrawal:2026-10-10:8a1b…",
  "input": {
    "facilityCode": "TUN-1",
    "reason": "end_of_season",
    "lines": [{ "sku": "TSHIRT-RED-M", "qty": 30 }]
  }
}

قراءة عدّ المستودع

GraphQL
query StockedPositions($cursor: String, $updatedSince: DateTime) {
  apiStockedPositions(cursor: $cursor, limit: 200, updatedSince: $updatedSince) {
    items { sku offerId facilityCode onHandQty reservedQty availableQty updatedAt }
    nextCursor
  }
}

مع SDK Node

@wamia/connect-sdk يوفر دوال منمّطة لكل عملية أعلاه، مفتاحية حيث الواجهة مفتاحية، إضافة إلى isInboundEvent وisWithdrawalEvent لتضييق حمولات الويب هوك.

TypeScript
import { ConnectClient, idempotencyKeyFor } from '@wamia/connect-sdk';

const client = new ConnectClient({ apiKey: process.env.WAMIA_API_KEY! });

// 1. The warehouse count wins for stocked offers: mirror it, never declare it.
for await (const position of client.allStockedPositions({ updatedSince: lastSync })) {
  await setShopStock(position.sku, position.availableQty);
}
const result = await client.declareStocks(crossDockRows, idempotencyKeyFor('stock', crossDockRows));
for (const item of result.items) {
  if (item.skipped) log(`${item.sku}: kept the warehouse count (${item.skipReason})`);
}

// 2. Declare a top-up, then book the first free slot of the next two weeks.
const shipment = await client.declareInbound(
  { lines: [{ offerId, qty: 120 }], inboundMode: 'SELLER_DROPOFF', sellerReference: 'PO-77' },
  idempotencyKeyFor('inbound:PO-77', [offerId]),
);
const days = await client.inboundSlotGrid(shipment.facilityId, from, to);
const slot = days.flatMap((day) => day.slots).find((s) => s.remaining > 0);
if (slot) await client.bookInboundAppointment(shipment.id, slot.dockId, slot.startsAt);

// 3. Ask for stock back; the warehouse approves, picks, then hands it over.
await client.requestWithdrawal(
  { facilityCode: 'TUN-1', lines: [{ sku: 'TSHIRT-RED-M', qty: 30 }], reason: 'end_of_season' },
  idempotencyKeyFor('withdrawal:2026-10-10', ['TSHIRT-RED-M']),
);

حالات التوريد

الحالةالمعنى
DRAFTمُعلنة بلا موعد بعد (أو فات الموعد الأخير).
SCHEDULEDفترة رصيف محجوزة؛ scheduledAt وappointmentId مضبوطان.
PICKED_UPWAMIA_PICKUP فقط: جمع السائق البضاعة.
DROPPED_OFFوصلت البضاعة إلى الرصيف وتنتظر العدّ.
RECEIVINGالمستودع يعدّ؛ receivedUnits يرتفع سطراً سطراً.
RECEIVEDمَعدودة؛ الفروقات إن وُجدت تنتظر ردّ المتجر.
CLOSEDالمخزون قابل للبيع؛ تحرّكت المراكز.
CANCELLEDألغاها المتجر قبل الوصول أو ألغاها المستودع.

حالات السحب

الحالةالمعنى
REQUESTEDبانتظار قرار المستودع.
APPROVEDمقبول؛ الوحدات محجوزة. احجزوا موعداً عند الرصيف.
SCHEDULEDحُجز موعد عند الرصيف؛ يصفه appointment.
PREPARINGالمستودع يحضّر الوحدات.
READYمحضَّر وينتظر المتجر عند الرصيف.
ARRIVEDالمتجر عند الرصيف؛ التسليم جارٍ.
CLOSEDسُلّم كاملاً إلى shopRepresentative؛ تحرّكت المراكز.
HANDED_OVERسُلّم جزئياً؛ كل سطر يحمل shortfallReason.
NO_SHOWمرّت الفترة دون حضور المتجر؛ أعيدوا الحجز قبل holdUntil (stagedHoldHours).
REJECTEDمرفوض؛ اقرأ decisionNote.
CANCELLEDألغاه المتجر قبل التحضير أو المستودع أو بعد انتهاء مدة الاحتفاظ.

الأحداث

النوعالمعنى
inbound.declaredأُعلنت شحنة (shipmentId).
inbound.scheduledحُجز الموعد الأول (appointmentId).
inbound.rescheduledنُقلت إلى فترة أخرى (previousAppointmentId).
inbound.slot_closedأُغلق المستودع على الفترة المحجوزة؛ أعد الحجز (appointmentId).
inbound.arrivedسُجّل الوصول عند الرصيف.
inbound.receivedعُدّت؛ اسحب المراكز.
inbound.discrepancyنقص أو فائض أو تلف يحتاج رداً.
inbound.no_showمرّت الفترة بلا وصول؛ أعد الحجز.
inbound.cancelledأُلغيت.
withdrawal.requestedطُلب سحب (withdrawalId).
withdrawal.decidedقُبل أو رُفض؛ اقرأ الطلب لمعرفة status وdecisionNote.
withdrawal.scheduledحُجز موعد عند الرصيف أو نُقل (appointmentId).
withdrawal.slot_closedأُغلق المستودع على الفترة المحجوزة؛ أعد الحجز (appointmentId).
withdrawal.readyمحضَّر وينتظر عند الرصيف.
withdrawal.handed_overسُلّم (كاملاً أو مع نقص)؛ اسحب المراكز.
withdrawal.no_showمرّت الفترة دون حضور المتجر؛ أعد الحجز.
withdrawal.cancelledأُلغي؛ تحررت الوحدات.