# حساب المتجر: كيف يُسجَّل، ومِمَّ يتكوّن

> هذه الوثيقة تشرح **الحساب الجاري لكل متجر**: متى تُكتب كل حركة، وبأي إشارة، ومن يملك
> تسجيلها، وكيف يُحتسب الرصيد. مكتوبة للمحاسبة والإدارة المالية — لا تحتاج خلفية برمجية
> لقراءتها.

---

## ١. لماذا وُجد هذا الحساب أصلاً

قبله كان لدينا شيئان، ولم يكن أيٌّ منهما يجيب عن السؤال الأهم:

| الموجود سابقاً | ماذا يفعل | لماذا لا يكفي |
|---|---|---|
| **كشف الفترة** (`statement`) | تقرير محسوب لحظة فتحه عن فترة محددة | **لا يتراكم.** ما لم يُسوَّ هذا الشهر يختفي من الشاشة حين يبدأ الشهر التالي |
| **جدول التسويات** (`settlements`) | سجل الدفعات التي صُرفت | لم يكن يُطرح من الكشف، فلا يُعرف ما تبقّى |

فكان السؤال **«كم يدين لنا هذا المتجر الآن؟»** بلا إجابة في أي مكان من النظام.

والحالة التي تكشف العطل بوضوح هي **المتجر المدفوع مسبقاً**: عملاؤه يدفعون له مباشرة، فنحن لا
نحصّل منهم شيئاً، ومع ذلك نحمّله أجور التوصيل على كل طلب. رصيد هذا المتجر يتحرك **باتجاه واحد
فقط — نزولاً** — وكان هذا النزول يتبخّر مع كل دورة شهرية.

**الحساب الجاري** هو السجل التراكمي الذي يجيب عن ذلك السؤال. يُخزَّن في جدول
`store_ledger_entries`، وهو جدول **إضافي فقط** (append-only): لا يُعدَّل سطر بعد كتابته، ولا
يُحذف — التصحيح يكون بسطر جديد، لا بتعديل القديم.

---

## ٢. القاعدة التي يقوم عليها كل شيء: الإشارة

> ### المبلغ الموجب يعني: **نحن ندين للمتجر.**

قراءتها بالمقلوب تقلب كل رصيد في النظام، فهي القاعدة الوحيدة التي يجب حفظها.

وهي **نفس قاعدة حساب الكابتن** عن قصد: من تعلّم قراءة رصيد واحد يقرأ الآخر بالطريقة ذاتها.

- النقد الذي قبضناه من عميل المتجر → **يرفع** الرصيد (المال ماله، ونحن نحتفظ به له).
- أجور التوصيل التي نتقاضاها → **تخفض** الرصيد.
- متجر رصيده صفر = **مُصفّى**، لا له ولا عليه.

### الإشارة لا يحدّدها من يُسجّل، بل نوع الحركة نفسه

الإشارة مثبَّتة في تعريف كل نوع، لا تُمرَّر مع الطلب. والسبب عملي: من يُطلب منه أن يتذكّر اتجاه
الحركة سيخطئ يوماً ما، **والإشارة الخاطئة لا تُظهر خطأً** — بل تُبلِّغ بهدوء أن أحدهم دائن بمال
ليس له. فلا يستطيع أي جزء من النظام تسجيل «أجور توصيل» ترفع رصيد المتجر.

الاستثناء الوحيد هو **التسوية اليدوية**، وسيأتي شرحها.

---

## ٣. أنواع الحركات الخمسة

| النوع | الرمز | الإشارة | معناه | من يكتبه |
|---|---|:---:|---|---|
| محصّل من عملائه | `cod_collected` | **+** | قبضنا نقداً من عميله عند التسليم، ونحتفظ به له | الطلب، تلقائياً |
| أجور التوصيل | `delivery_fee` | **−** | ما نتقاضاه منه مقابل التوصيل | الطلب، تلقائياً |
| مدفوع إليه | `paid_to_store` | **−** | حوّلنا له مالاً | موظف، يدوياً |
| مقبوض منه | `received_from_store` | **+** | حوّل لنا مالاً | موظف، يدوياً |
| تسويات | `adjustment` | **±** | تصحيح، بإشارة يحدّدها المُسجِّل | موظف، يدوياً |

**الحركتان الأوليان تُكتبان بالطلب ولا تُكتبان بيد أحد أبداً.** لا توجد شاشة تسمح بإدخال أجر
توصيل أو مبلغ محصّل يدوياً — لأن كليهما نتيجةُ تسليمٍ حدث، لا رقمٌ يُقرَّر.

---

## ٤. ما الذي يكتبه الطلب تلقائياً

تُكتب الحركات **داخل نفس المعاملة (transaction) التي تغيّر حالة الطلب**. أي أن تغيّر الحالة
وكتابة المال **يحدثان معاً أو لا يحدث أيٌّ منهما**. لا يوجد وضع يصبح فيه الطلب «مُسلَّم» دون أثر
مالي، ولا العكس.

### الحالة الأولى: تسليم ناجح، والدفع نقداً عند الاستلام

يُكتب **سطران، لا سطر واحد**:

```
+ ٩٣٠    محصّل من عملائه      ← نقد العميل، صار في عهدتنا
−  ٨٠    أجور التوصيل         ← حصّتنا
─────
+ ٨٥٠    صافي أثر هذا الطلب على رصيده
```

**لماذا سطران وليس سطراً صافياً بقيمة ٨٥٠؟** لأن الدمج يُضيع موضوع النزاع. حين يسأل المتجر
لاحقاً، يكون سؤاله عن أحد النصفين: إما «كم قبضتم من عميلي؟» أو «لماذا أجر التوصيل بهذا
المقدار؟» — وسطرٌ واحد مدموج لا يجيب عن أيٍّ منهما.

**أي مبلغ يُعتمد؟** المبلغ الذي **أدخله الكابتن فعلياً** (`amount_collected`) وليس المبلغ
المتوقَّع. فلو كان المتوقَّع ٩٣٠ وقبض الكابتن ٩٠٠، يُقيَّد للمتجر **٩٠٠** — لأنه المبلغ الذي
نحتفظ به بالفعل. وإن لم يُسجَّل مبلغ فعلي (طلبات قديمة سابقة لوجود هذا الحقل) يُرجَع إلى المبلغ
المتوقَّع حتى لا يبقى الطلب بلا قيد.

### الحالة الثانية: تسليم ناجح، والطلب مدفوع مسبقاً

يُكتب **سطر واحد فقط**:

```
−  ٨٠    أجور التوصيل
```

لم نقبض من العميل شيئاً، فليس هناك ما يُقيَّد له. وهذا السطر السالب المنفرد هو — حرفياً — السبب
الذي بُني هذا الحساب من أجله.

### الحالة الثالثة: تسليم فاشل

يُكتب **سطر واحد: أجر التوصيل فقط.**

لم ينتقل نقد في أي اتجاه، فلا يوجد ما يُقيَّد للمتجر. لكن الكابتن قاد إلى العميل فعلاً، فالرحلة
وقعت، والمتجر يُحمَّل كلفتها.

> **قرار عمل، لا قرار تقني:** هذه هي النقطة الوحيدة التي **يفترق فيها ما يُحمَّل على المتجر عمّا
> يُدفع للكابتن**. وهي مسجّلة في سجل التغييرات بوصفها سياسة.

**ويُكتب سبب الفشل على السطر نفسه** — وهذا ليس تزييناً. سطرٌ يقول «−٨٠ · أجور توصيل» أمام طلب
لم يصل هو أكثر سطر يُسأل عنه. وجود «العميل غير متاح للتواصل» بجانبه يجيب عن السؤال قبل أن
يُطرح.

---

## ٥. ما الذي **لا** يُسجَّل — وهذا لا يقل أهمية

أربع حالات لا تكتب شيئاً، ولكلٍّ سببها:

| الحالة | لماذا لا تُسجَّل |
|---|---|
| **طلب بلا متجر** | طلب أُنشئ من لوحة الإدارة لا يعود لأي متجر، فليس هناك حساب يُحمَّل |
| **رحلة تجميع** (`collection leg`) | تنقل البضاعة إلى نقطة التغليف، لا تحمل مالاً بأي اتجاه، ولا يُحتسب عليها أجر — وهو الاستثناء نفسه الذي يطبّقه سلّم التسعير |
| **طلب سبق أن كُتبت سطوره** | حماية من **الازدواج**: إعادة محاولة تسليم لا يجوز أن تُحمِّل المتجر مرتين |
| **أجر توصيل صفر أو مُعفى** | الصفر ليس حركة. كشفٌ مليء بالأصفار كشفٌ لا يُقرأ |

---

## ٦. الحركات التي يسجّلها موظف بيده

### تسجيل دفعة (`settle`)

مبلغ **انتقل فعلاً** — حوالة أُرسلت أو نقد قُبض.

**المبلغ يُدخَل موجباً دائماً.** لا يُكتب مبلغ سالب في خانة الدفعة إطلاقاً؛ النظام يرفضه.

**والاتجاه لا يختاره الموظف.** الخادم يحسب — مع رصيد كل عملة — الحركةَ الوحيدة التي تُقرّب هذا
الحساب من الصفر، ويرسلها مع الرصيد تحت اسم `settle_with`:

- رصيد المتجر **موجب** (نحن ندين له) → الدفعة **مدفوع إليه** `−`
- رصيد المتجر **سالب** (هو يدين لنا) → الدفعة **مقبوض منه** `+`
- رصيد **مُصفّى** → لا توجد حركة تُقرّبه، وزرّ التسجيل معطَّل

والشاشة تقرأ هذا الاتجاه وتعرضه للموظف، ثم ترسله كما هو. فالموظف يُدخل **المبلغ والمرجع فقط**.

> **تفصيل تقني يهمّ من يتكامل مع الواجهة مباشرة:** الاتجاه حقلٌ **إلزامي** في الطلب
> (`type`)، ومقبولٌ فيه قيمتان لا ثالثة لهما: `paid_to_store` أو `received_from_store`. أي أن
> الخادم لا يستنتجه من الرصيد، بل **يُملي**ه عبر `settle_with` ويتحقق منه عند الاستقبال. وسبب
> هذا التقسيم أن أي شاشة تقرّر الاتجاه بنفسها ستعرض يوماً ما الاتجاه المعاكس — ودفعةٌ تُسجَّل
> بإشارة معكوسة **تُضاعف الدَّين بدل أن تسدّده**.

ويُطلب **مرجع الحوالة** حتى يمكن السؤال عن هذا السطر لاحقاً.

### تصحيح الحساب (`adjust`)

الحالة الوحيدة التي **يحدّد فيها المُسجِّل الإشارة بنفسه**، لأن التصحيح بطبيعته لا اتجاه ثابت
له.

وعليه يُحاط بقيدين لا يُتجاوزان:

1. **ملاحظة إلزامية.** تصحيح بلا سبب مكتوب **يُرفض**. رصيد تغيّر دون أن يُعرف لماذا هو رصيد لا
   يُوثق به.
2. **المبلغ لا يكون صفراً.**

### من يملك ماذا

| الصلاحية | ماذا تتيح |
|---|---|
| `store_ledger.view` | رؤية الأرصدة وكشوف الحسابات |
| `store_ledger.settle` | تسجيل دفعة أو تصحيح |

**مفصولتان عن قصد:** معرفة أي المتاجر متأخرة وظيفةٌ، وتعديل رصيد متجر وظيفةٌ أخرى.

### والتسويات الدورية؟

حين تُعتمد تسوية دورية (`settlement`) وتنتقل إلى حالة **مُسوّاة**، يُكتب لها سطر في الحساب
تلقائياً، بإشارة مشتقّة من صافي التسوية.

وهي **محصَّنة ضد الازدواج**: كل تسوية تحمل مرجعاً فريداً بشكل `settlement:<المعرّف>`، ويُفحص
وجوده قبل الكتابة. فلو اعتُمدت التسوية مرتين، لا يُكتب السطر إلا مرة واحدة.

---

## ٧. كيف يُحتسب الرصيد

كل سطر يحمل حقلين:

- `amount` — أثر هذه الحركة وحدها، بإشارتها
- `balance_after` — **الرصيد بعدها مباشرة**

أي أن الرصيد **لا يُجمَع عند العرض**، بل يُخزَّن محسوباً على كل سطر. وفائدة ذلك أن كشف الحساب
يُظهر الرصيد الجاري عند كل حركة، كما في كشف البنك تماماً.

### الرصيد **لكل عملة على حدة**

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

### الحماية من التزاحم

عند كتابة أي سطر، يُقرأ آخر رصيد لهذا المتجر **في هذه العملة** مع **قفل** (`lockForUpdate`)
داخل المعاملة. فلو سُلِّم طلبان لنفس المتجر في اللحظة ذاتها، ينتظر أحدهما الآخر، ولا يُبنى
السطران على رصيد قديم واحد.

### المبالغ ليست «مُدخَلات»

حقلا `amount` و`balance_after` **غير قابلين للتعبئة من أي طلب** (not fillable). تُحسب في طبقة
واحدة وتُكتب قسراً. وهذا ما يجعل **ادّعاء رصيد عبر الـ API مستحيلاً** لا ممنوعاً فحسب.

---

## ٨. حالات الرصيد الثلاث

| الحالة | المعنى | المتوقَّع |
|---|---|---|
| **له علينا** (`owed`) | رصيد موجب — نحتفظ بنقد عملائه | نحوّل له |
| **عليه لنا** (`owes`) | رصيد سالب — أجورنا تجاوزت ما نحتفظ به له | نطالبه |
| **مُصفّى** (`clear`) | صفر | لا إجراء |

**عتبة ٠٫٠١:** أي رصيد أقل من قرش واحد بأي اتجاه يُعدّ صفراً. والسبب أن باقي تقريب لا يجوز أن
يضع متجراً على قائمة المطالبات.

---

## ٩. أين يُقرأ هذا كله

### لوحة الإدارة

| الشاشة | ماذا تعرض |
|---|---|
| `/payments/stores` | كل المتاجر التي لها رصيد، **الأكبر رصيداً أولاً بغضّ النظر عن الاتجاه** — لأن السؤال الأول هو أين يجلس أكبر مبلغ، لا من يدين به. مع إجماليات لكل عملة |
| `/payments/stores/{المعرّف}` | حساب متجر واحد: الرصيد لكل عملة، ممّا يتكوّن، وكشف الحساب كاملاً، وزرّا **تسجيل دفعة** و**تصحيح الحساب** |

**ترتيب القائمة مقصود:** متجر عليه ٤٠٠٬٠٠٠ أهمّ من متجر له ٨٠، والترتيب بالقيمة المطلقة هو ما
يُظهر ذلك. ومرشّح **«عليه لنا»** يختصر القائمة إلى قائمة المطالبات.

**الإجماليات تصف كل المتاجر، لا الصفحة المعروضة** — وهي موضوعة بجانب الصفحة تحديداً حتى لا
يُصغّر التنقّل بين الصفحات الأرقامَ في عين القارئ.

### واجهات الخدمة (API)

**للإدارة:**

```
GET  /store-ledger                                  قائمة الأرصدة
GET  /store-clients/{المعرّف}/ledger                حساب متجر
GET  /store-clients/{المعرّف}/ledger/entries        كشف حسابه
POST /store-clients/{المعرّف}/ledger/settle         تسجيل دفعة
POST /store-clients/{المعرّف}/ledger/adjust         تصحيح
```

التسجيل والتصحيح لهما **مسار مستقل لكل منهما**، لا مسار واحد يأخذ «النوع» في الطلب — لأن
جعل النوع قيمةً في جسم الطلب يجعل **حدّ الصلاحية قيمةً في جسم الطلب** أيضاً.

**لبوابة المتجر** (يرى المتجر حسابه هو فقط):

```
GET /payments/balance      رصيده الجاري
GET /payments/ledger       كشف حسابه
GET /payments/statement    كشف الفترة (التقرير القديم، ما زال قائماً)
```

> **ملاحظة أمنية:** المتجر يُستنتج من **المستخدم المسجَّل دخوله**، لا من معرّف يُرسله في الطلب.
> متجرٌ يستطيع تسمية متجر هو متجرٌ يستطيع قراءة حساب غيره.

---

## ١٠. البيانات التاريخية

قاعدة بيانات أقدم من هذه الشاشة ستُظهر كل متجر وكأنه يبدأ من **صفر**، وهو رقم خاطئ.

لذلك يوجد أمر يعيد تشغيل التاريخ:

```bash
php artisan store-ledger:backfill
php artisan store-ledger:backfill --dry-run    # تجربة دون كتابة
```

يمرّ على كل طلب **مُسلَّم أو فاشل** يعود لمتجر، وعلى كل تسوية **مُسوّاة**، ويكتب ما كان يجب أن
يُكتب وقتها.

وخاصّتان فيه تستحقان الذكر:

- **`--dry-run` تنفيذ حقيقي يُلغى في آخره** (داخل معاملة تُسترجع)، لا تقديرٌ نظري. وقد كان
  تقديراً في البداية فأعطى ٥ بينما الواقع ١٠ — فالطريقة الوحيدة لمعرفة ما سيحدث هي أن يحدث ثم
  يُسترجع.
- **تشغيله مرتين آمن.** الطلبات التي كُتبت سطورها تُتخطّى. أول تشغيل على البيانات الحقيقية كتب
  ١٠ سطور، والثاني كتب صفراً.

> **إجراء مطلوب:** هذا الأمر **لم يُشغَّل على بيئة الإنتاج بعد.** قبل اعتماد الأرصدة في أي
> مطالبة أو تحويل، يجب تشغيله مرة واحدة — وإلا بدأ كل متجر من تاريخ غير تاريخه.

---

## ١١. ضمانات السلامة

| الضمان | كيف يُنفَّذ |
|---|---|
| المال والحالة لا ينفصلان | الكتابة داخل معاملة تغيير حالة الطلب |
| لا تحميل مزدوج على طلب | فحص وجود سطور للطلب قبل الكتابة |
| لا تسوية مزدوجة | مرجع فريد لكل تسوية، يُفحص قبل الكتابة |
| لا رصيد يُدّعى من الخارج | `amount` و`balance_after` غير قابلين للتعبئة |
| لا رصيد يُبنى على قراءة قديمة | قفل على آخر سطر داخل المعاملة |
| **حذف المتجر لا يمحو الدَّين** | المفتاح الأجنبي **مقيَّد** (`restrictOnDelete`) لا متتالياً — كل ارتباط آخر بالمتجر يجوز أن يزول معه لأنه يصفه، أمّا هذا فيسجّل أن مالاً مستحق |
| حذف الطلب لا يمحو أثره المالي | يصبح السطر بلا طلب (`nullOnDelete`) ويبقى في الرصيد |
| لا تعديل بأثر رجعي | الجدول إضافي فقط — لا يوجد حقل `updated_at` أصلاً |
| تصحيح بلا سبب مرفوض | الملاحظة إلزامية على التسويات |

---

## ١٢. مثال كامل: متجر على مدى أسبوع

متجر يتعامل بالليرة السورية، أجر التوصيل ٨٠:

| اليوم | ما حدث | السطر | المبلغ | الرصيد بعده |
|---|---|---|---:|---:|
| الأحد | طلب نقدي، سُلِّم، قُبض ٩٣٠ | محصّل من عملائه | **+٩٣٠** | ٩٣٠ |
| الأحد | أجر توصيل الطلب نفسه | أجور التوصيل | **−٨٠** | ٨٥٠ |
| الاثنين | طلب مدفوع مسبقاً، سُلِّم | أجور التوصيل | **−٨٠** | ٧٧٠ |
| الثلاثاء | طلب فشل — العميل غير متاح | أجور التوصيل | **−٨٠** | ٦٩٠ |
| الأربعاء | حوّلنا له ٦٠٠ | مدفوع إليه | **−٦٠٠** | ٩٠ |
| الخميس | تبيّن أن أجر الثلاثاء حُمِّل خطأً | تسويات *(بملاحظة)* | **+٨٠** | ١٧٠ |

**الحالة النهائية: «له علينا ١٧٠ ل.س»** — نحتفظ بـ ١٧٠ من ماله وعلينا تحويلها.

ولاحظ في السطر الأخير أن التصحيح **أُضيف سطراً جديداً**، ولم يُعدَّل سطر الثلاثاء ولم يُحذف.
فيبقى في الكشف أثرُ ما حدث **وأثرُ تصحيحه**، وهو ما يجعل الكشف قابلاً للتدقيق.

### ولو كان المتجر مدفوعاً مسبقاً بالكامل

| اليوم | السطر | المبلغ | الرصيد بعده |
|---|---|---:|---:|
| الأحد | أجور التوصيل | −٨٠ | −٨٠ |
| الاثنين | أجور التوصيل | −٨٠ | −١٦٠ |
| الثلاثاء | أجور التوصيل | −٨٠ | −٢٤٠ |

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

---

## ملخّص في ستة أسطر

1. **موجب = نحن ندين للمتجر.** القاعدة الوحيدة الواجب حفظها.
2. **الطلب يكتب حركاته بنفسه** — نقدي: سطران، مدفوع مسبقاً أو فاشل: سطر واحد.
3. **أجر التوصيل والمبلغ المحصّل لا يُدخَلان يدوياً أبداً.**
4. **الدفعة تُدخَل موجبة**، واتجاهها يأتي من الرصيد لا من المُدخِل.
5. **التصحيح وحده يحمل إشارة يختارها المُسجِّل، ولا يُقبل بلا سبب مكتوب.**
6. **رصيد لكل عملة.** لا جمع بين عملتين.
