نظام إدارة الطاولات يبدو بسيطاً حتى تضيف الحجوزات المتداخلة والزبائن بدون حجز والتحديثات الفورية. هذا كيف نبني هذا الخادم بـ Go لمشغلي المطاعم في لبنان والشرق الأوسط.
كيف تبني نظام إدارة الطاولات والحجوزات للمطاعم باستخدام Go
نظام إدارة الطاولات يبدو بسيطاً في ظاهره حتى تضيف الحجوزات المتداخلة، والزبائن الذين يصلون بدون حجز ويحتاجون نفس الطاولات، وموظفي المطبخ الذين يحتاجون رؤية التغييرات فوراً. نموذج البيانات الأساسي صغير بشكل مخادع، لكن الحالات الحدية في بيئة المطعم الحية تجعله صعباً حقاً.
نموذج البيانات الأساسي
ثلاثة كيانات رئيسية: الطاولات، الحجوزات، وتعيينات الطاولات.
CREATE TABLE tables (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL,
name TEXT NOT NULL,
capacity INT NOT NULL,
section TEXT,
is_active BOOLEAN NOT NULL DEFAULT true
);
CREATE TABLE reservations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL,
guest_name TEXT NOT NULL,
guest_phone TEXT,
party_size INT NOT NULL,
reserved_for TIMESTAMPTZ NOT NULL,
duration_mins INT NOT NULL DEFAULT 90,
status TEXT NOT NULL DEFAULT 'confirmed'
);
CREATE TABLE table_assignments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL,
reservation_id UUID REFERENCES reservations(id),
table_id UUID NOT NULL REFERENCES tables(id),
starts_at TIMESTAMPTZ NOT NULL,
ends_at TIMESTAMPTZ NOT NULL,
assignment_type TEXT NOT NULL DEFAULT 'reservation'
);
جدول table_assignments هو مصدر الحقيقة لما هو مشغول خلال أي نافذة زمنية. كل من الحجوزات والزبائن بدون حجز ينشئون تعيينات.
الكشف عن التعارضات
الاستعلام الحرج: قبل تعيين طاولة، تحقق مما إذا كان أي تعيين موجود يتداخل مع النافذة الزمنية المطلوبة.
func (r *TableRepository) HasConflict(
ctx context.Context,
tenantID, tableID uuid.UUID,
startsAt, endsAt time.Time,
) (bool, error) {
query := `
SELECT EXISTS (
SELECT 1 FROM table_assignments
WHERE tenant_id = $1
AND table_id = $2
AND starts_at < $4
AND ends_at > $3
)
`
var exists bool
err := r.db.QueryRowContext(ctx, query, tenantID, tableID, startsAt, endsAt).Scan(&exists)
return exists, err
}
شرط تداخل النافذة الزمنية starts_at < ends_at_new AND ends_at > starts_at_new هو الفحص القياسي لتداخل الفترات. يجب أن يعمل هذا الفحص داخل معاملة مع الإدراج، باستخدام SELECT ... FOR UPDATE على سجل الطاولة لمنع الطلبات المتزامنة من الاثنتين من تجاوز فحص التعارض.
التعامل مع الزبائن بدون حجز (Walk-in)
الزبائن بدون حجز هم تعيينات بدون حجز. يُنشئون عندما يصل ضيف بدون حجز. المدة تُقدَّر (عادةً 60-90 دقيقة):
func (s *TableService) SeatWalkIn(
ctx context.Context,
tenantID, tableID uuid.UUID,
estimatedDurationMins int,
) (*TableAssignment, error) {
now := time.Now()
a := TableAssignment{
TenantID: tenantID,
TableID: tableID,
StartsAt: now,
EndsAt: now.Add(time.Duration(estimatedDurationMins) * time.Minute),
Type: "walk_in",
}
return &a, s.tables.AssignTable(ctx, a)
}
الاستعلام عن الطاولات المتاحة
عند أخذ حجز أو فحص التوفر لزبون بدون حجز، تحتاج جميع الطاولات ذات السعة الكافية التي لا تحتوي على تعيينات متعارضة:
SELECT t.id, t.name, t.capacity, t.section
FROM tables t
WHERE t.tenant_id = $1
AND t.capacity >= $2
AND t.is_active = true
AND NOT EXISTS (
SELECT 1 FROM table_assignments a
WHERE a.table_id = t.id
AND a.starts_at < $4
AND a.ends_at > $3
)
ORDER BY t.capacity ASC
الترتيب تصاعدياً حسب السعة يُرجع أصغر طاولة تناسب الحفلة أولاً، وهو مهم لتحسين استخدام الطاولات.
عرض الطوابق في الوقت الفعلي
عرض الطابق يُظهر جميع الطاولات مع حالتها الحالية. طاولة إما متاحة، أو مشغولة، أو محجوزة قريباً (تعيين قادم خلال 30 دقيقة)، أو محجوزة مستقبلاً.
للتحديثات الفورية، نستخدم PostgreSQL LISTEN/NOTIFY لدفع تغييرات حالة الطاولة إلى العملاء الويب المتصلين بدلاً من الاستطلاع المستمر. عند إنشاء تعيين أو تحديثه أو حذفه، تُطلق دالة مُشغِّلة إشعاراً تستمع إليه goroutine في Go وتُعيده عبر WebSocket.
الدروس المستفادة من الإنتاج
- استخدم جدول
table_assignmentsمنفصلاً كمصدر حقيقة للإشغال، وليس عموداً للحالة على جدول الطاولات. - يجب أن يعمل الكشف عن التعارضات داخل معاملة مع قفل صف لضمان السلامة من حالات السباق.
- الزبائن بدون حجز والحجوزات كلاهما سجل
table_assignmentsبنوع حقل مختلف. - ادفع تغييرات الحالة عبر WebSocket بدلاً من الاستطلاع.
نبني أنظمة مطاعم تشغيلية للعملاء في لبنان وعبر الشرق الأوسط. إذا كنت تبني ميزات POS أو حجوزات وتريد الحصول على البنية الصحيحة، تواصل معنا عبر https://voxire.com/get-a-quote/
Voxire
تطوير منتجات SaaS
من الفكرة إلى المنتج المطلوق - استراتيجية وبنية ومطوّر كامل الخدمات.
تعرف على المزيد


