########################################
# SALES ORDERS AUTO JOURNAL ENTRIES
# القيود المالية التلقائية لطلبات الصالون وطلبات التطبيق
########################################

You are an expert backend engineer AND accountant.
We already have:
- Orders module (طلبات الصالون + طلبات التطبيق)
- Accounting tables: journal_entries, journal_lines
- Basic accounts config for purchases.

Now we want to implement **automatic journal entries** for:
- Salon orders (طلبات الصالون)
- App orders (طلبات التطبيق)
- Cost of goods sold (COGS) when products are sold
- Payments of orders
- Packages that contain products/services

All entries will appear in the **Financial Journals page (صفحة القيود المالية)**.


========================================
# 1) BASE CONFIG – ACCOUNT CODES
========================================

Add / extend a simple config object for accounts (if not already):

```js
const ACCOUNTS = {
  customers: "1100",        // العملاء
  cash: "1110",             // الصندوق
  bank: "1120",             // البنك (بطاقات / تحويل)
  wallet: "1130",           // المحفظة / رصيد العميل (اختياري)

  salon_revenue: "4100",    // إيرادات الصالون
  vat_output: "2411",       // الضريبة المحصلة

  inventory: "1201",        // مخزون البضاعة
  cogs: "5100",             // تكلفة المبيعات
};
Keep it in a central place like: src/config/accounts.js and use it in the accounting service.

========================================

2) JOURNAL MODEL (تذكير سريع)
========================================

We already have:

Table: journal_entries
id

entry_number (string, unique) // مثال: "SO-0001"

date (date)

description (string, nullable)

created_at

updated_at

Table: journal_lines
id

journal_entry_id (FK)

account_code (string, not null)

debit (real, not null, default 0)

credit (real, not null, default 0)

Business rule:
For each journal_entry → sum(debit) MUST equal sum(credit).

========================================

3) SALES JOURNAL FOR ORDERS
قيد المبيعات لطلبات الصالون وطلبات التطبيق
========================================

When an order (salon or app) is completed (تم تنفيذ الطلب بالكامل):

We create a Sales Journal Entry.

Assume example order total is 1150 SAR VAT inclusive, with:

Net revenue (without VAT) = 1000

VAT collected = 150

3.1 Accounting entry (when order is created on customer account)
القيد المالي لطلب صالون أو طلب تطبيق:

الحساب	مدين	دائن
العملاء	1150	0
إيرادات الصالون	0	1000
الضريبة المحصلة	0	150

In terms of accounts config:

Debit ACCOUNTS.customers with 1150

Credit ACCOUNTS.salon_revenue with 1000

Credit ACCOUNTS.vat_output with 150

This entry is created once when the order is posted / completed (حالة الطلب تتحول إلى مكتمل).

========================================

4) PAYMENT JOURNAL FOR ORDERS
قيد سداد الفاتورة حسب طريقة الدفع
========================================

عندما يتم سداد الفاتورة نفسها، يكون هناك قيد سداد بقيمة نفس مبلغ الطلب، ويكون من الزبون وحسابه في قيد العملاء ثم يأتي الدفع حسب طريقة الدفع (كاش أو بطاقة أو غيره).

We need a separate payment journal entry when payment is recorded.

4.1 Full payment (سداد كامل)
If payment amount = full order total (1150) and payment method is:

CASH → use ACCOUNTS.cash

CARD/BANK → use ACCOUNTS.bank

WALLET → use ACCOUNTS.wallet

Example for CASH:

الحساب	مدين	دائن
الصندوق (كاش)	1150	0
العملاء	0	1150

So:

Debit ACCOUNTS.cash 1150

Credit ACCOUNTS.customers 1150

4.2 Partial payment (سداد جزئي)
If paid 500 and remaining 650:

قيد المبيعات (كما في القسم 3):

العملاء مدين 1150

إيرادات الصالون دائن 1000

الضريبة المحصلة دائن 150

قيد سداد جزئي:

الصندوق مدين 500

العملاء دائن 500

The remaining 650 stays in customers account (رصيد عميل).

========================================

5) COGS ENTRY WHEN ORDER HAS PRODUCTS
تكلفة البضاعة المباعة للمنتجات
========================================

إذا كان طلب الصالون يحتوي على منتجات، يجب إنشاء قيد تكلفة البضاعة المباعة بناءً على تكلفة المنتج من المخزون.

Example:
Order contains products whose total cost (from inventory) = 50 SAR.

القيد المالي يكون كالتالي:

الحساب	مدين	دائن
تكلفة المبيعات	50	0
مخزون البضاعة	0	50

So:

Debit ACCOUNTS.cogs 50

Credit ACCOUNTS.inventory 50

Cost calculation:

Use the product's current average_unit_cost (متوسط التكلفة) * quantity sold
OR

Use detailed batch cost if already implemented.

This COGS journal is in addition to the Sales journal entry.

========================================

6) PACKAGES (الحزم) LINKED WITH PRODUCTS/SERVICES
========================================

If an order line is a Package (حزمة) that contains products and/or services:

For revenue + VAT: treat the package like a normal service sale (price of package).

For COGS:

If the package includes products:

Sum the cost of the products included in the package (again from average cost or batches).

Create the same COGS entry:

Debit ACCOUNTS.cogs

Credit ACCOUNTS.inventory

If the package contains services only (no stock products):

Do NOT create COGS entry.

Example:

If package cost from included products is 50 SAR:

مخزون البضاعة دائن 50

تكلفة المبيعات مدين 50

========================================

7) IMPLEMENTATION DETAILS
========================================

Create a dedicated service:
src/services/accountingService.js

Add functions:

js
نسخ الكود
async function createSalesJournalForOrder(orderId) { ... }
async function createPaymentJournalForOrder(orderId, paymentId) { ... }
async function createCogsJournalForOrder(orderId) { ... }
async function reverseJournalForOrder(orderId) { ... } // for cancellation
7.1 createSalesJournalForOrder(orderId)
Behavior:

Load the order with:

total_amount

vat_amount

subtotal_amount (net)

customer_id

Compute:

revenue = subtotal_amount

vat = vat_amount

total = total_amount

Create journal_entries row:

date = order.completed_at (or order_date)

entry_number = "SO-" + padded order id

description = قيد طلب صالون رقم ${order.id} للعميل ${customer.name}

Insert journal_lines:

Debit ACCOUNTS.customers with total_amount

Credit ACCOUNTS.salon_revenue with subtotal_amount

Credit ACCOUNTS.vat_output with vat_amount

Validate:

Sum of debits == sum of credits, otherwise throw error with message:

"إجمالي المدين لا يساوي إجمالي الدائن في قيد الطلب".

7.2 createPaymentJournalForOrder(orderId, paymentId)
Assume we have payments table for orders:

order_payments or similar.

Steps:

Load payment:

amount

payment_date

payment_method (cash, bank, card, wallet)

Determine debit account:

cash → ACCOUNTS.cash

card/bank → ACCOUNTS.bank

wallet → ACCOUNTS.wallet

Create journal_entries row:

date = payment_date

entry_number = "RCV-" + padded payment id

description = سداد من العميل لطلب رقم ${order.id}

journal_lines:

Debit debitAccount (cash/bank/wallet) with amount

Credit ACCOUNTS.customers with amount

7.3 createCogsJournalForOrder(orderId)
Steps:

Load all product lines (not services) from this order.

For each product line:

quantity_sold

product.average_unit_cost (or computed cost)

total_cost = sum(quantity_sold * average_unit_cost)

If total_cost > 0:

Create journal_entries row:

date = order.completed_at

entry_number = "COGS-" + order id

description = تكلفة المبيعات لطلب رقم ${order.id}

journal_lines:

Debit ACCOUNTS.cogs with total_cost

Credit ACCOUNTS.inventory with total_cost

========================================

8) ORDER CANCELLATION – REVERSAL ENTRY
========================================

If an order is cancelled after posting its journal entries:

We must create a reversal entry that reverses all lines of the original entry.

Procedure:

Find all journal_entries related to this order
(you can link them via reference_type = "Order" and reference_id = order.id).

For each journal_entry:

Create a new journal_entry with:

description = "عكس قيد طلب رقم X"

For each journal_line:

Create a new line with swapped debit and credit:

debit_new = credit_old

credit_new = debit_old

Mark the original journal_entry as reversed:

Add column is_reversed (boolean) and set it to true, OR

Keep relation to reversal entry.

Use Arabic error messages:

"لا يمكن إلغاء الطلب لأنه لم يتم إنشاء قيده المالي"

"لا يمكن إنشاء قيد عكسي لهذا الطلب"

========================================

9) API INTEGRATION
========================================

Hook the accounting functions into order lifecycle:

When order status changes to "completed":

Call:

createSalesJournalForOrder(orderId)

createCogsJournalForOrder(orderId)

When a payment is recorded for an order:

Call:

createPaymentJournalForOrder(orderId, paymentId)

When an order is cancelled:

Call:

reverseJournalForOrder(orderId)

Make sure any errors in accounting are logged and returned with clear Arabic messages.

########################################

END OF SPEC
