التوريدات والسحوبات
العروض بنمط التجهيز 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 هما لحظتا تحرّك المراكز؛ أي ويب هوك من إحدى العائلتين مُحفّز جيد لسحب تزايدي.
مثال
إعلان تزويد
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 }]
}
}الفترات والحجز والتغيير
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
}
}طلب سحب
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 }]
}
}قراءة عدّ المستودع
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 لتضييق حمولات الويب هوك.
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_UP | WAMIA_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 | أُلغي؛ تحررت الوحدات. |