Get a quote

تصميم تقارير نهاية الوردية ونهاية اليوم لأنظمة نقاط البيع في Go

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

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

ما الذي يجب أن يحتويه تقرير الوردية؟

مدراء المطاعم والمتاجر اللبنانية لديهم تصور واضح لما يجب أن يُظهره ملخص الوردية. ليس مجرد قائمة معاملات، بل أداة توفيق. التقرير يجب أن يُجيب على:

  • كم يجب أن يكون في الدرج من نقود؟
  • كم أُخذ بالبطاقة، وهل يتطابق مع إجمالي الطرفية؟
  • كم عملية إلغاء جرت، ومن أذن بها؟
  • ما الخصومات التي طُبقت ولمن؟

تقرير مفيد يشمل: المبيعات الإجمالية حسب الفئة، الخصومات وأنواعها، الإلغاءات والمردودات مع تحديد المشغل، صافي المبيعات بعد الخصومات والإلغاءات، المبيعات حسب طريقة الدفع، النقد المتوقع في الدرج، وعدد المعاملات ومتوسط قيمة المعاملة.

تصميم نموذج بيانات الوردية

الوردية هي نافذة زمنية محددة يملكها أمين صندوق أو مدير، مرتبطة بطرفية ومكان محدد:

CREATE TABLE shifts (
    id           bigserial PRIMARY KEY,
    tenant_id    bigint NOT NULL,
    location_id  bigint NOT NULL,
    terminal_id  bigint NOT NULL,
    cashier_id   bigint NOT NULL,
    opened_by    bigint NOT NULL,
    closed_by    bigint,
    opened_at    timestamptz NOT NULL DEFAULT now(),
    closed_at    timestamptz,
    opening_float decimal(12, 2) NOT NULL DEFAULT 0,
    status       text NOT NULL DEFAULT 'open'
);

CREATE TABLE shift_summaries (
    id                    bigserial PRIMARY KEY,
    shift_id              bigint NOT NULL REFERENCES shifts(id),
    gross_sales           decimal(12, 2) NOT NULL,
    total_discounts       decimal(12, 2) NOT NULL,
    total_voids           decimal(12, 2) NOT NULL,
    total_refunds         decimal(12, 2) NOT NULL,
    net_sales             decimal(12, 2) NOT NULL,
    cash_sales            decimal(12, 2) NOT NULL,
    card_sales            decimal(12, 2) NOT NULL,
    transaction_count     int NOT NULL,
    void_count            int NOT NULL,
    expected_cash_in_drawer decimal(12, 2) NOT NULL,
    computed_at           timestamptz NOT NULL DEFAULT now()
);

جدول shift_summaries يُخزن القيم المحسوبة مسبقاً عند الإغلاق. لا تُعيد حساب إجماليات الوردية من المعاملات الخام بعد الإغلاق أبداً. الملخص هو السجل الموثوق لما حدث خلال تلك الوردية.

حساب إجماليات الوردية بشكل صحيح

الخطأ الأكثر شيوعاً في حساب تقرير الوردية هو التعامل مع الإلغاءات بشكل خاطئ. الإلغاء يُلغي معاملة بالكامل. المردود هو معاملة سالبة منفصلة. الرياضيات مختلفة:

for rows.Next() {
    var order Order
    rows.Scan(&order)

    if order.Status == "voided" {
        // إلغاء: تتبعه منفصلاً دون احتسابه ضمن المبيعات الإجمالية
        totals.TotalVoids = totals.TotalVoids.Add(order.Total)
        totals.VoidCount++
        continue
    }

    if order.Status == "refunded" {
        totals.TotalRefunds = totals.TotalRefunds.Add(order.Total)
        totals.RefundCount++
    }

    totals.GrossSales = totals.GrossSales.Add(order.Subtotal)
    totals.TotalDiscounts = totals.TotalDiscounts.Add(order.DiscountAmount)
    totals.TransactionCount++

    switch order.PaymentMethod {
    case "cash":
        totals.CashSales = totals.CashSales.Add(order.Total)
    case "card":
        totals.CardSales = totals.CardSales.Add(order.Total)
    }
}

totals.NetSales = totals.GrossSales.
    Sub(totals.TotalDiscounts).
    Sub(totals.TotalVoids).
    Sub(totals.TotalRefunds)

إغلاق الوردية بشكل ذري

إغلاق الوردية عملية من خطوتين: حساب الإجماليات وتسجيل حالة الإغلاق. يجب أن يحدثا في معاملة واحدة:

func (s *ShiftService) CloseShift(ctx context.Context, shiftID int64, closedByID int64) (*ShiftSummary, error) {
    tx, err := s.db.BeginTx(ctx, nil)
    if err != nil {
        return nil, err
    }
    defer tx.Rollback()

    var shift Shift
    err = tx.QueryRowContext(ctx, `
        SELECT id, status, opening_float FROM shifts
        WHERE id = $1 AND tenant_id = $2 FOR UPDATE
    `, shiftID, s.tenantID).Scan(&shift.ID, &shift.Status, &shift.OpeningFloat)
    if err != nil {
        return nil, err
    }

    if shift.Status == "closed" {
        return nil, ErrShiftAlreadyClosed
    }

    totals, err := s.computeTotalsInTx(ctx, tx, shiftID)
    if err != nil {
        return nil, err
    }

    expectedCash := shift.OpeningFloat.Add(totals.CashSales)

    // إدراج الملخص
    _, err = tx.ExecContext(ctx, `
        INSERT INTO shift_summaries (...) VALUES (...)
    `, ...)

    // تحديث حالة الوردية
    _, err = tx.ExecContext(ctx, `
        UPDATE shifts SET status = 'closed', closed_at = now(), closed_by = $1 WHERE id = $2
    `, closedByID, shiftID)

    return &ShiftSummary{Totals: totals, ExpectedCash: expectedCash}, tx.Commit()
}

التعامل مع طرق الدفع المتعددة

في المطاعم اللبنانية، مشهد الدفع متشعب. وردية واحدة قد تشمل: نقد بالدولار، نقد بالليرة اللبنانية، بطاقة ائتمان عبر شركة محلية، ودفع رقمي عبر تطبيق محفظة. لكل طريقة دفع آثار تشغيلية مختلفة للتوفيق.

CREATE TABLE order_tenders (
    id          bigserial PRIMARY KEY,
    order_id    bigint NOT NULL,
    shift_id    bigint NOT NULL,
    method      text NOT NULL,   -- 'cash_usd', 'cash_lbp', 'card_visa', 'wallet_app'
    amount      decimal(12, 2) NOT NULL,
    currency    char(3) NOT NULL DEFAULT 'USD',
    exchange_rate decimal(10, 6),
    reference   text
);

دروس تشغيلية من عملاء المطاعم اللبنانية

ثلاثة أنماط تظهر باستمرار عبر عمليات نشر نظام نقاط البيع في المطاعم اللبنانية:

المدراء يُغلقون الورديات في أوقات غير منتظمة. وردية قد تستمر 6 ساعات أو 14 ساعة حسب ازدحام المطعم. نموذج بيانات الوردية يجب أن يتعامل مع الورديات المفتوحة عبر منتصف الليل دون إفساد الإجماليات اليومية.

تفويض الإلغاء مصدر دائم للاحتكاك التشغيلي. الإلغاءات يجب أن تتطلب رقم سري للمدير أو تجاوز المشرف. كل إلغاء يجب أن يُسجل من أذن به.

انقطاع الاتصال أثناء إغلاق الوردية. الشبكات اللبنانية غير مستقرة. عملية إغلاق الوردية يجب أن تكون قابلة للتكرار الآمن: إذا فقدت طرفية نقاط البيع الاتصال في منتصف الطلب وضغط المدير إغلاقاً مرة أخرى، فيجب أن تُكشف المحاولة الثانية على الحالة المغلقة بالفعل وتُعيد الملخص الموجود.

الخلاصة

احسب وخزّن ملخصات الوردية عند الإغلاق. لا تُعد الحساب من المعاملات الخام. تعامل مع الإلغاءات والمردودات كأنواع سجل مستقلة. استخدم SELECT FOR UPDATE لمنع محاولات الإغلاق المتزامنة. ادعم تتبع العطاءات متعددة العملات من اليوم الأول.

العودة إلى المدونة
Chat on WhatsApp