-
Loading…
+if (!is_logged_in()) {
+ render_header('بوابة ERP', 'نظام ERP ويب على PHP + MySQL مع تسجيل دخول وصلاحيات ومخزون ومشتريات ومبيعات وتصنيع ومحاسبة.', 'dashboard');
+ ?>
+
+
+
+
ERP Access Portal
+
نظام ERP ويب جاهز كبداية تشغيلية متكاملة.
+
تم تجهيز نواة تشغيلية تشمل تسجيل الدخول، الصلاحيات، العملاء، الموردين، الأصناف، المشتريات، المبيعات، التصنيع، المحاسبة، وسجل حركات المخزون. ادخل بحساب مناسب لفتح لوحة التحكم.
+
+
+
+
+
حسابات تجريبية
+
+
= e($cred['username']) ?> / = e($cred['password']) ?>
+
+
هذه الحسابات لأغراض البداية فقط.
+
+
+
+
+ 0 ? round(($inventory['raw_qty'] / $inventory['all_qty']) * 100, 1) : 0;
+$finishedPercent = $inventory['all_qty'] > 0 ? round(($inventory['finished_qty'] / $inventory['all_qty']) * 100, 1) : 0;
+$totalManufactured = 0.0;
+foreach (fetch_records('manufacturing_order') as $order) {
+ $totalManufactured += (float)($order['payload_data']['produced_qty'] ?? 0);
+}
+
+render_header('لوحة ERP المركزية', 'لوحة تشغيل يومية للعملاء والموردين والأصناف والمبيعات والمشتريات والتصنيع والمحاسبة وحركات المخزون.', 'dashboard');
+?>
+
+
+
+
Phase 5 ERP Foundation
+
تشغيل يومي موحّد مع تقارير فورية لكل أقسام الـ ERP.
+
مرحبًا = e($current['full_name'] ?? '') ?> — هذه النسخة تدعم تسجيل الدخول والصلاحيات، الموردين، أوامر الشراء، مسار المبيعات الكامل، أوامر التصنيع، المحاسبة التشغيلية، وسجل حركات المخزون، مع صفحة تقارير جديدة لتجميع الأداء حسب الفترة.
+
-
= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.
-
This page will update automatically as the plan is implemented.
-
Runtime: PHP = htmlspecialchars($phpVersion) ?> — UTC = htmlspecialchars($now) ?>
-
-
- Page updated: = htmlspecialchars($now) ?> (UTC)
-
-
-
+
+
+
الدور الحالي
+
= e(role_label((string)($current['role'] ?? ''))) ?>
+
إجمالي السجلات التشغيلية: = e((string)(array_sum($counts))) ?>
+
آخر تحديث: = e(date('Y-m-d H:i')) ?> UTC
+
+
+
+
+
+
+
+
+ العملاء
= e((string)$counts['customer']) ?>
سجل العملاء
+ الموردون
= e((string)$counts['supplier']) ?>
ربط المشتريات
+ الأصناف
= e((string)$counts['product']) ?>
مواد خام ونهائية
+ أوامر التصنيع
= e((string)($counts['manufacturing_order'] ?? 0)) ?>
= e((string)$manufacturingToday) ?> اليوم
+ مبيعات اليوم
= e((string)$salesToday) ?>
= e(format_money(today_sales_value())) ?>
+ مشتريات اليوم
= e((string)$purchasesToday) ?>
= e(format_money(today_purchase_value())) ?>
+ المقبوضات
= e(format_money((float)$financial['customer_receipts'])) ?>
اليوم = e(format_money((float)$financial['today_receipts'])) ?>
+ المصروفات
= e(format_money((float)$financial['expenses'])) ?>
اليوم = e(format_money((float)$financial['today_expenses'])) ?>
+
+
+
+
+
+
+
+
إجمالي المخزون
= e((string)$inventory['all_qty']) ?>
= e((string)$inventory['sku_count']) ?> صنف نشط
+
إجمالي الإنتاج المرحّل
= e((string)$totalManufactured) ?>
ناتج أوامر التصنيع المكتملة
+
ذمم العملاء
= e(format_money((float)$financial['receivables'])) ?>
من فواتير المبيعات
+
ذمم الموردين
= e(format_money((float)$financial['payables'])) ?>
من المشتريات المستلمة
+
الربح المتوقع
= e(format_money((float)$financial['expected_profit'])) ?>
فواتير - مشتريات - مصروفات
+
صافي التدفق النقدي
= e(format_money((float)$financial['net_cashflow'])) ?>
المقبوضات - المدفوعات - المصروفات
+
+
+
الخامات (= e((string)$rawPercent) ?>%) = e((string)$inventory['raw_qty']) ?>
+
+
المنتجات النهائية (= e((string)$finishedPercent) ?>%) = e((string)$inventory['finished_qty']) ?>
+
+
+
+
+
+
+
+
+
+
+
+
= e($product['title']) ?> = e($payload['sku'] ?? $product['code']) ?>
+
المتوفر: = e((string)($payload['stock_qty'] ?? 0)) ?> — حد الطلب: = e((string)($payload['reorder_level'] ?? 0)) ?>
+
+
+
+
+
لا توجد أصناف منخفضة حاليًا.
+
+
+
+
+
+
+
+
+
+
+
+
+ الرقم العميل الصنف الإجمالي
+
+
+ = e($order['code']) ?> = e($payload['customer_name'] ?? '') ?> = e($payload['product_name'] ?? '') ?> = e(format_money((float)($payload['grand_total'] ?? 0))) ?>
+
+
+
+
+
+
لا توجد أوامر بيع بعد.
+
+
+
+
+
+
+
+
+
+ الرقم المورد الصنف الإجمالي
+
+
+ = e($order['code']) ?> = e($payload['supplier_name'] ?? '') ?> = e($payload['product_name'] ?? '') ?> = e(format_money((float)($payload['grand_total'] ?? 0))) ?>
+
+
+
+
+
+
لا توجد أوامر شراء بعد.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
= e($order['code']) ?> = e($payload['finished_product_name'] ?? '') ?>
+
= e((string)($payload['produced_qty'] ?? 0)) ?> = e($payload['finished_unit'] ?? '') ?>
+
+
+
+
+
لا توجد أوامر تصنيع بعد.
+
+
+
+
+
+
+
+
+
+
+
= e($payload['product_name'] ?? '') ?> = e($payload['movement_type'] ?? '') ?> — = e($payload['reference_code'] ?? '') ?>
+
= e(movement_direction_label($delta)) ?> = e((string)$delta) ?> / بعد الحركة = e((string)($payload['qty_after'] ?? 0)) ?>
+
+
+
+
+
لا توجد حركات مخزون بعد.
+
+
+
+
+
+
+
+
+
+
+
= e($row['title']) ?> = e($row['code']) ?>
+
= e(format_money((float)($payload['amount'] ?? 0))) ?>
+
+
+
+
+
لا توجد قيود محاسبية بعد.
+
+
+
+
+
diff --git a/login.php b/login.php
new file mode 100644
index 0000000..1f4fbbd
--- /dev/null
+++ b/login.php
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
= e($error) ?>
+
+
+
+
حسابات تجريبية جاهزة
+
+
= e($cred['username']) ?> / = e($cred['password']) ?> — = e(role_label($cred['role'])) ?>
+
+
للاستخدام الأولي فقط — غيّر كلمات المرور لاحقًا عند إضافة إدارة المستخدمين.
+
+
+
+
+
diff --git a/logout.php b/logout.php
new file mode 100644
index 0000000..e9a4402
--- /dev/null
+++ b/logout.php
@@ -0,0 +1,7 @@
+ 0 && $rawProductId === $finishedProductId) {
+ $errors[] = 'لا يمكن أن تكون المادة الخام والمنتج النهائي نفس الصنف.';
+ }
+ if ($finishedQty <= 0 || $actualRawQty <= 0) {
+ $errors[] = 'أدخل كميات صحيحة للإنتاج والاستهلاك الفعلي.';
+ }
+ if (!array_key_exists($qualityStatus, $qualityOptions)) {
+ $errors[] = 'حالة الجودة غير صحيحة.';
+ }
+ if (!in_array($status, ['draft', 'completed'], true)) {
+ $errors[] = 'حالة أمر التصنيع غير صحيحة.';
+ }
+
+ if (!$errors && $rawProduct && $finishedProduct) {
+ $rawPayload = $rawProduct['payload_data'];
+ $finishedPayload = $finishedProduct['payload_data'];
+ $producedQty = $qualityStatus === 'rejected' ? 0.0 : $finishedQty;
+
+ db()->beginTransaction();
+ try {
+ $manufacturingId = create_record('manufacturing_order', 'أمر تصنيع ' . $finishedProduct['title'], next_code('MO', 'manufacturing_order'), [
+ 'raw_product_id' => (int)$rawProduct['id'],
+ 'raw_product_name' => $rawProduct['title'],
+ 'raw_sku' => $rawPayload['sku'] ?? $rawProduct['code'],
+ 'raw_unit' => $rawPayload['unit'] ?? 'وحدة',
+ 'finished_product_id' => (int)$finishedProduct['id'],
+ 'finished_product_name' => $finishedProduct['title'],
+ 'finished_sku' => $finishedPayload['sku'] ?? $finishedProduct['code'],
+ 'finished_unit' => $finishedPayload['unit'] ?? 'وحدة',
+ 'finished_qty' => $finishedQty,
+ 'actual_raw_qty' => $actualRawQty,
+ 'produced_qty' => $producedQty,
+ 'quality_status' => $qualityStatus,
+ 'quality_label' => manufacturing_quality_label($qualityStatus),
+ 'conversion_ratio' => $actualRawQty > 0 ? round($finishedQty / $actualRawQty, 4) : 0,
+ 'notes' => $notes,
+ 'created_date' => date('Y-m-d H:i'),
+ 'completed_at' => $status === 'completed' ? date('Y-m-d H:i') : null,
+ 'created_by' => current_user()['username'] ?? 'system',
+ ], $status);
+
+ $order = fetch_record('manufacturing_order', $manufacturingId);
+ if ($status === 'completed' && $order) {
+ $rawStock = adjust_product_stock($rawProduct, -$actualRawQty, 'manufacturing_consume', $order['code'], 'manufacturing_order', $manufacturingId, 'استهلاك خامات لأمر التصنيع ' . $order['code']);
+ $payload = $order['payload_data'];
+ $payload['raw_stock_after'] = $rawStock['after'];
+
+ if ($producedQty > 0) {
+ $finishedStock = adjust_product_stock($finishedProduct, $producedQty, 'manufacturing_output', $order['code'], 'manufacturing_order', $manufacturingId, 'إضافة إنتاج نهائي لأمر التصنيع ' . $order['code']);
+ $payload['finished_stock_after'] = $finishedStock['after'];
+ } else {
+ $currentFinishedStock = (float)($finishedPayload['stock_qty'] ?? 0);
+ $payload['finished_stock_after'] = $currentFinishedStock;
+ }
+
+ update_record_payload($manufacturingId, $payload, 'completed');
+ }
+
+ db()->commit();
+ set_flash('success', $status === 'completed' ? 'تم إكمال أمر التصنيع وتحديث المخزون تلقائيًا.' : 'تم حفظ أمر التصنيع كمسودة.');
+ redirect('manufacturing.php?id=' . $manufacturingId);
+ } catch (Throwable $e) {
+ db()->rollBack();
+ $errors[] = 'تعذر حفظ أمر التصنيع، تأكد من توفر المخزون الخام ثم حاول مرة أخرى.';
+ }
+ }
+}
+
+$rawMaterials = raw_material_dataset();
+$finishedProducts = finished_product_dataset();
+$orders = fetch_records('manufacturing_order');
+$detail = isset($_GET['id']) ? fetch_record('manufacturing_order', (int)$_GET['id']) : null;
+$todayCount = today_record_count('manufacturing_order');
+$completedCount = 0;
+$draftCount = 0;
+$totalProducedQty = 0.0;
+foreach ($orders as $order) {
+ $payload = $order['payload_data'];
+ if (($order['status'] ?? '') === 'completed') {
+ $completedCount++;
+ }
+ if (($order['status'] ?? '') === 'draft') {
+ $draftCount++;
+ }
+ $totalProducedQty += (float)($payload['produced_qty'] ?? 0);
+}
+
+render_header('التصنيع', 'تسجيل أوامر تصنيع تخصم الخامات وتضيف المنتجات النهائية تلقائيًا مع متابعة الجودة.', 'manufacturing');
+?>
+
+
أوامر التصنيع
= e((string)count($orders)) ?>
إجمالي السجل
+
اليوم
= e((string)$todayCount) ?>
أوامر اليوم
+
مكتملة
= e((string)$completedCount) ?>
تم ترحيلها للمخزون
+
الإنتاج الناتج
= e((string)$totalProducedQty) ?>
إجمالي الكمية المقبولة
+
+
+
+
+
+
+
+
يلزم وجود مادة خام ومنتج نهائي واحد على الأقل في صفحة الأصناف قبل بدء التصنيع.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ الرقم المنتج النهائي الخامة الناتج الحالة
+
+
+
+ = e($order['code']) ?>
+ = e($payload['finished_product_name'] ?? '') ?>= e((string)($payload['finished_qty'] ?? 0)) ?> = e($payload['finished_unit'] ?? '') ?>
+ = e($payload['raw_product_name'] ?? '') ?>= e((string)($payload['actual_raw_qty'] ?? 0)) ?> = e($payload['raw_unit'] ?? '') ?>
+ = e((string)($payload['produced_qty'] ?? 0)) ?> = e($payload['finished_unit'] ?? '') ?>= e(manufacturing_quality_label((string)($payload['quality_status'] ?? 'accepted'))) ?>
+ = e(order_status_label((string)$order['status'])) ?>
+
+
+
+
+
+
+
لا توجد أوامر تصنيع بعد.
+
+
+
+
+
+
+
رقم الأمر = e($detail['code']) ?>
+
الحالة = e(order_status_label((string)$detail['status'])) ?>
+
الجودة = e(manufacturing_quality_label((string)($payload['quality_status'] ?? 'accepted'))) ?>
+
المنشئ = e($payload['created_by'] ?? '') ?>
+
+
+
المادة الخام = e($payload['raw_product_name'] ?? '') ?> — = e($payload['raw_sku'] ?? '') ?>
+
الاستهلاك الفعلي = e((string)($payload['actual_raw_qty'] ?? 0)) ?> = e($payload['raw_unit'] ?? '') ?>
+
المخزون بعد الخصم = e((string)($payload['raw_stock_after'] ?? '—')) ?>
+
+
+
المنتج النهائي = e($payload['finished_product_name'] ?? '') ?> — = e($payload['finished_sku'] ?? '') ?>
+
الكمية المنتجة = e((string)($payload['finished_qty'] ?? 0)) ?> = e($payload['finished_unit'] ?? '') ?>
+
الكمية المرحلة فعليًا = e((string)($payload['produced_qty'] ?? 0)) ?> = e($payload['finished_unit'] ?? '') ?>
+
المخزون بعد الإضافة = e((string)($payload['finished_stock_after'] ?? '—')) ?>
+
+
+
تاريخ الإنشاء = e($payload['created_date'] ?? '') ?>
+
تاريخ الإكمال = e($payload['completed_at'] ?? '—') ?>
+
نسبة التحويل = e((string)($payload['conversion_ratio'] ?? 0)) ?>
+
مسودات = e((string)$draftCount) ?>
+
+
= e($payload['notes']) ?>
+
+
اختر أمر تصنيع من الجدول لعرض التفاصيل.
+
+
+
+
+
diff --git a/print_order.php b/print_order.php
new file mode 100644
index 0000000..4238396
--- /dev/null
+++ b/print_order.php
@@ -0,0 +1,109 @@
+ 'عرض سعر قابل للطباعة والحفظ PDF من المتصفح',
+ 'sales_order' => 'أمر بيع مؤكد وقابل للطباعة أو الحفظ PDF',
+ 'delivery_note' => 'أمر تسليم جاهز للطباعة مع بيانات العميل والصنف',
+ 'sales_invoice' => 'فاتورة مبيعات أولية قابلة للطباعة أو الحفظ PDF',
+ default => 'مستند مبيعات قابل للطباعة',
+};
+$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? ('Printable ' . $documentLabel);
+$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
+?>
+
+
+
+
+
+
= e($document['code']) ?> | = e(app_name()) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Printable Sales Document
+
= e(app_name()) ?>
+
= e($documentLabel) ?> — = e($documentSubtitle) ?>
+
+
+
= e($document['code']) ?>
+
= e($payload['created_date'] ?? '') ?>
+
+
+
+
+
+
+
بيانات العميل
+
= e($payload['customer_name'] ?? '') ?>
+
الفرع: = e($payload['branch'] ?? '') ?>
+
+
+
+
+
بيانات المستند
+
الحالة: = e(order_status_label((string)$document['status'])) ?>
+
نوع المستند: = e($documentLabel) ?>
+
+
المصدر: = e((string)$payload['source_document_label']) ?> — = e((string)$payload['source_document_code']) ?>
+
+
+
+
+
+
+
+ الصنف SKU الكمية سعر الوحدة الإجمالي
+
+
+ = e($payload['product_name'] ?? '') ?>
+ = e($payload['sku'] ?? '') ?>
+ = e((string)($payload['qty'] ?? 0)) ?> = e($payload['unit'] ?? '') ?>
+ = e(format_money((float)($payload['unit_price'] ?? 0))) ?>
+ = e(format_money((float)($payload['subtotal'] ?? 0))) ?>
+
+
+
+
+
+
+
+
+
الإجمالي قبل الضريبة = e(format_money((float)($payload['subtotal'] ?? 0))) ?>
+
الضريبة 15% = e(format_money((float)($payload['vat'] ?? 0))) ?>
+
الإجمالي النهائي = e(format_money((float)($payload['grand_total'] ?? 0))) ?>
+
+
+
+
+
= e($document['record_type'] === 'delivery_note' ? 'توقيع المستلم' : 'توقيع العميل') ?>
+
ملاحظات
= e($payload['notes']) ?>
+
+
+
+
+
diff --git a/products.php b/products.php
new file mode 100644
index 0000000..598970a
--- /dev/null
+++ b/products.php
@@ -0,0 +1,194 @@
+ $sku,
+ 'unit' => $unit,
+ 'category' => $category,
+ 'stock_qty' => (float)$stockQty,
+ 'reorder_level' => (float)$reorder,
+ 'sale_price' => (float)$salePrice,
+ 'notes' => $notes,
+ ]);
+ set_flash('success', 'تمت إضافة الصنف بنجاح.');
+ redirect('products.php');
+ }
+}
+
+$products = fetch_records('product');
+$detail = isset($_GET['id']) ? fetch_record('product', (int)$_GET['id']) : null;
+render_header('الأصناف والمخزون', 'إدارة الأصناف الجاهزة للبيع ومتابعة المخزون الحالي وحدود إعادة الطلب.', 'products');
+?>
+
+
+
+
+
+
+
+
+
+
+ اسم الصنف
+
+
+
+
+
+ الفئة
+
+ منتج نهائي
+ مواد خام
+
+
+
+ المخزون الحالي
+
+
+
+
+
+ ملاحظات
+
+
+ حفظ الصنف
+
+
+
+
+
+
+
+
+
+
+ الصنف
+ الفئة
+ المخزون
+ السعر
+ تفاصيل
+
+
+
+
+
+
+ = e($product['title']) ?>
+ = e($payload['sku'] ?? $product['code']) ?>
+
+ = e($payload['category'] ?? '') ?>
+
+ = e((string)($payload['stock_qty'] ?? 0)) ?>
+ منخفض
+
+ = e(format_money((float)($payload['sale_price'] ?? 0))) ?>
+ عرض
+
+
+
+
+
+
+
+
+
+
+
+ اسم الصنف
+ = e($detail['title']) ?>
+
+
+ SKU
+ = e($payload['sku'] ?? $detail['code']) ?>
+
+
+ المخزون الحالي
+ = e((string)($payload['stock_qty'] ?? 0)) . ' ' . e($payload['unit'] ?? '') ?>
+
+
+ حد إعادة الطلب
+ = e((string)($payload['reorder_level'] ?? 0)) ?>
+
+
+
+
+
+
+
الفئة
+
= e($payload['category'] ?? '') ?>
+
+
+
+
+
سعر البيع
+
= e(format_money((float)($payload['sale_price'] ?? 0))) ?>
+
+
+
+
= e($payload['notes'] ?? '') ?>
+
+
اختر صنفًا من الجدول لعرض تفاصيله.
+
+
+
+
+
diff --git a/purchases.php b/purchases.php
new file mode 100644
index 0000000..032bbfd
--- /dev/null
+++ b/purchases.php
@@ -0,0 +1,171 @@
+beginTransaction();
+ try {
+ $purchaseId = create_record('purchase_order', 'أمر شراء ' . $supplier['title'], next_code('PO', 'purchase_order'), [
+ 'supplier_id' => (int)$supplier['id'],
+ 'supplier_name' => $supplier['title'],
+ 'product_id' => (int)$product['id'],
+ 'product_name' => $product['title'],
+ 'sku' => $sku,
+ 'unit' => $productPayload['unit'] ?? 'وحدة',
+ 'qty' => $qty,
+ 'unit_cost' => $unitCost,
+ 'subtotal' => $subtotal,
+ 'vat' => $vat,
+ 'grand_total' => $grand,
+ 'notes' => $notes,
+ 'created_date' => date('Y-m-d H:i'),
+ 'received_at' => $status === 'received' ? date('Y-m-d H:i') : null,
+ 'created_by' => current_user()['username'] ?? 'system',
+ ], $status);
+
+ $purchase = fetch_record('purchase_order', $purchaseId);
+ if ($status === 'received' && $purchase) {
+ $stock = adjust_product_stock($product, $qty, 'purchase_receive', $purchase['code'], 'purchase_order', $purchaseId, 'استلام شراء من المورد ' . $supplier['title']);
+ $payload = $purchase['payload_data'];
+ $payload['stock_after'] = $stock['after'];
+ update_record_payload($purchaseId, $payload, 'received');
+ }
+
+ db()->commit();
+ set_flash('success', $status === 'received' ? 'تم إنشاء أمر الشراء واستلامه وتحديث المخزون.' : 'تم حفظ أمر الشراء كمسودة.');
+ redirect('purchases.php?id=' . $purchaseId);
+ } catch (Throwable $e) {
+ db()->rollBack();
+ $errors[] = 'تعذر حفظ أمر الشراء، حاول مرة أخرى.';
+ }
+ }
+}
+
+$suppliers = supplier_dataset();
+$products = product_dataset();
+$orders = fetch_records('purchase_order');
+$detail = isset($_GET['id']) ? fetch_record('purchase_order', (int)$_GET['id']) : null;
+render_header('أوامر الشراء', 'إنشاء أوامر شراء وربطها بالموردين وتحديث المخزون عند الاستلام.', 'purchases');
+?>
+
+
+
+
+
+
+
+
+
+
+ المورد
+
+ اختر المورد
+
+ = e($supplier['name']) ?> — = e($supplier['code']) ?>
+
+
+
+
+ الصنف
+
+ اختر الصنف
+
+ = e($product['name']) ?> — = e($product['sku']) ?>
+
+
+
+
+
الكمية
+
سعر الشراء
+
الحالة استلام الآن مسودة
+
+
+ ملاحظات
+
+
+ حفظ أمر الشراء
+
+
+
+
+
+
+
+
+
+ الرقم المورد الصنف الإجمالي الحالة
+
+
+
+ = e($order['code']) ?>
+ = e($payload['supplier_name'] ?? '') ?>
+ = e($payload['product_name'] ?? '') ?>= e((string)($payload['qty'] ?? 0)) ?> = e($payload['unit'] ?? '') ?>
+ = e(format_money((float)($payload['grand_total'] ?? 0))) ?>
+ = e(order_status_label((string)$order['status'])) ?>
+
+
+
+
+
+
+
لا توجد أوامر شراء بعد.
+
+
+
+
+
+
+
رقم الأمر = e($detail['code']) ?>
+
الحالة = e(order_status_label((string)$detail['status'])) ?>
+
المورد = e($payload['supplier_name'] ?? '') ?>
+
تاريخ الإنشاء = e($payload['created_date'] ?? '') ?>
+
+
+
الصنف = e($payload['product_name'] ?? '') ?> — = e($payload['sku'] ?? '') ?>
+
الكمية = e((string)($payload['qty'] ?? 0)) ?> = e($payload['unit'] ?? '') ?>
+
سعر الشراء = e(format_money((float)($payload['unit_cost'] ?? 0))) ?>
+
الإجمالي = e(format_money((float)($payload['grand_total'] ?? 0))) ?>
+
المخزون بعد الاستلام = e((string)($payload['stock_after'] ?? '—')) ?>
+
+
= e($payload['notes']) ?>
+
+
اختر أمر شراء من الجدول لعرض تفاصيله.
+
+
+
+
+
diff --git a/reports.php b/reports.php
new file mode 100644
index 0000000..15c8b15
--- /dev/null
+++ b/reports.php
@@ -0,0 +1,263 @@
+ $endDate) {
+ [$startDate, $endDate] = [$endDate, $startDate];
+}
+
+$salesInvoices = filter_records_by_date(fetch_records('sales_invoice'), $startDate, $endDate);
+$purchaseOrders = filter_records_by_date(fetch_records('purchase_order'), $startDate, $endDate);
+$receivedPurchases = array_values(array_filter($purchaseOrders, static fn (array $row): bool => (string)($row['status'] ?? '') === 'received'));
+$manufacturingOrders = filter_records_by_date(fetch_records('manufacturing_order'), $startDate, $endDate);
+$completedManufacturing = array_values(array_filter($manufacturingOrders, static fn (array $row): bool => (string)($row['status'] ?? '') === 'completed'));
+$customerPayments = filter_records_by_date(fetch_records('customer_payment'), $startDate, $endDate);
+$supplierPayments = filter_records_by_date(fetch_records('supplier_payment'), $startDate, $endDate);
+$expenses = filter_records_by_date(fetch_records('expense_entry'), $startDate, $endDate);
+$stockMovements = filter_records_by_date(fetch_records('stock_movement'), $startDate, $endDate);
+
+$salesTotal = 0.0;
+$topCustomers = [];
+foreach ($salesInvoices as $invoice) {
+ $payload = $invoice['payload_data'];
+ $amount = (float)($payload['grand_total'] ?? 0);
+ $salesTotal += $amount;
+ $customerName = (string)($payload['customer_name'] ?? 'عميل غير محدد');
+ if (!isset($topCustomers[$customerName])) {
+ $topCustomers[$customerName] = 0.0;
+ }
+ $topCustomers[$customerName] += $amount;
+}
+arsort($topCustomers);
+$topCustomers = array_slice($topCustomers, 0, 5, true);
+
+$purchaseTotal = 0.0;
+foreach ($receivedPurchases as $purchase) {
+ $purchaseTotal += (float)($purchase['payload_data']['grand_total'] ?? 0);
+}
+
+$manufacturedQty = 0.0;
+foreach ($completedManufacturing as $order) {
+ $manufacturedQty += (float)($order['payload_data']['produced_qty'] ?? 0);
+}
+
+$receiptsTotal = 0.0;
+foreach ($customerPayments as $payment) {
+ $receiptsTotal += (float)($payment['payload_data']['amount'] ?? 0);
+}
+
+$supplierPaymentsTotal = 0.0;
+foreach ($supplierPayments as $payment) {
+ $supplierPaymentsTotal += (float)($payment['payload_data']['amount'] ?? 0);
+}
+
+$expensesTotal = 0.0;
+$expenseBreakdown = [];
+foreach ($expenses as $expense) {
+ $payload = $expense['payload_data'];
+ $amount = (float)($payload['amount'] ?? 0);
+ $expensesTotal += $amount;
+ $category = expense_category_label((string)($payload['category'] ?? 'other'));
+ if (!isset($expenseBreakdown[$category])) {
+ $expenseBreakdown[$category] = 0.0;
+ }
+ $expenseBreakdown[$category] += $amount;
+}
+arsort($expenseBreakdown);
+
+$productActivity = [];
+foreach ($stockMovements as $movement) {
+ $payload = $movement['payload_data'];
+ $productName = (string)($payload['product_name'] ?? 'صنف غير محدد');
+ if (!isset($productActivity[$productName])) {
+ $productActivity[$productName] = 0.0;
+ }
+ $productActivity[$productName] += abs((float)($payload['qty_change'] ?? 0));
+}
+arsort($productActivity);
+$productActivity = array_slice($productActivity, 0, 5, true);
+
+$profitEstimate = $salesTotal - $purchaseTotal - $expensesTotal;
+$netCashflow = $receiptsTotal - $supplierPaymentsTotal - $expensesTotal;
+
+render_header('التقارير التنفيذية', 'تقارير ERP تشغيلية موحدة للمبيعات والمشتريات والتصنيع والمحاسبة مع فلترة حسب التاريخ.', 'reports');
+?>
+
+
+
+ فواتير المبيعات
= e(format_money($salesTotal)) ?>
= e((string)count($salesInvoices)) ?> فاتورة خلال الفترة
+ مشتريات مستلمة
= e(format_money($purchaseTotal)) ?>
= e((string)count($receivedPurchases)) ?> أمر شراء مستلم
+ إنتاج مكتمل
= e(number_format($manufacturedQty, 2)) ?>
= e((string)count($completedManufacturing)) ?> أمر تصنيع مكتمل
+ حركات المخزون
= e((string)count($stockMovements)) ?>
إجمالي الإضافات والخصومات
+ مقبوضات العملاء
= e(format_money($receiptsTotal)) ?>
= e((string)count($customerPayments)) ?> حركة قبض
+ مدفوعات الموردين
= e(format_money($supplierPaymentsTotal)) ?>
= e((string)count($supplierPayments)) ?> حركة دفع
+ المصروفات
= e(format_money($expensesTotal)) ?>
= e((string)count($expenses)) ?> قيد مصروف
+ صافي النقدية
= e(format_money($netCashflow)) ?>
مقبوضات - مدفوعات - مصروفات
+
+
+
+
+
+
+
+
+
+ الرقم العميل الإجمالي التاريخ
+
+
+
+ = e($invoice['code']) ?>
+ = e($payload['customer_name'] ?? '') ?>
+ = e(format_money((float)($payload['grand_total'] ?? 0))) ?>
+ = e(record_created_date($invoice)) ?>
+
+
+
+
+
+
+
لا توجد فواتير مبيعات ضمن الفترة المحددة.
+
+
+
+
+
+
+
+
+
أوامر شراء مستلمة
+
+
+
= e($purchase['code']) ?> = e(format_money((float)($payload['grand_total'] ?? 0))) ?>
+
+
+
لا توجد مشتريات مستلمة في هذه الفترة.
+
+
+
+
+
+
أوامر تصنيع مكتملة
+
+
+
= e($order['code']) ?> = e((string)($payload['produced_qty'] ?? 0)) ?> = e($payload['finished_unit'] ?? '') ?>
+
+
+
لا توجد أوامر تصنيع مكتملة في هذه الفترة.
+
+
+
+
+
+
+
+
+
+ strcmp((string)($b['created_at'] ?? ''), (string)($a['created_at'] ?? ''))); ?>
+
+
+
+
+ النوع الوصف القيمة التاريخ
+
+
+
+ = e(match ((string)$row['record_type']) { 'customer_payment' => 'مقبوض', 'supplier_payment' => 'مدفوع', 'expense_entry' => 'مصروف', default => (string)$row['record_type'] }) ?>
+ = e($row['title']) ?>
+ = e(format_money((float)($payload['amount'] ?? 0))) ?>
+ = e(record_created_date($row)) ?>
+
+
+
+
+
+
+
لا توجد حركة مالية ضمن الفترة المحددة.
+
+
+
+
+
+
+
+
+
الربح المتوقع = e(format_money($profitEstimate)) ?>
+
صافي التدفق النقدي = e(format_money($netCashflow)) ?>
+
المبيعات - المشتريات = e(format_money($salesTotal - $purchaseTotal)) ?>
+
إجمالي المصروفات = e(format_money($expensesTotal)) ?>
+
+
+
+
+
+
+
+ $amount): ?>
+
= e($customerName) ?> = e(format_money($amount)) ?>
+
+
+
+
لا توجد بيانات مبيعات كافية لحساب أفضل العملاء.
+
+
+
+
+
+
+
+ $qty): ?>
+
= e($productName) ?> = e(number_format($qty, 2)) ?>
+
+
+
+
لا توجد حركات مخزون ضمن الفترة المحددة.
+
+
+
+
+
+
+
+ $amount): ?>
+
= e($category) ?> = e(format_money($amount)) ?>
+
+
+
+
لا توجد مصروفات ضمن الفترة المحددة.
+
+
+
+
+
diff --git a/sales_orders.php b/sales_orders.php
new file mode 100644
index 0000000..4544f4b
--- /dev/null
+++ b/sales_orders.php
@@ -0,0 +1,375 @@
+ (int)$customer['id'],
+ 'customer_name' => $customer['title'],
+ 'branch' => $branch,
+ 'product_id' => (int)$product['id'],
+ 'product_name' => $product['title'],
+ 'sku' => $productPayload['sku'] ?? $product['code'],
+ 'unit' => $productPayload['unit'] ?? 'وحدة',
+ 'qty' => $qty,
+ 'unit_price' => $unitPrice,
+ 'subtotal' => $subtotal,
+ 'vat' => $vat,
+ 'grand_total' => $grand,
+ 'notes' => $notes,
+ 'document_type' => $documentType,
+ 'document_label' => sales_document_label($documentType),
+ 'created_date' => date('Y-m-d H:i'),
+ 'created_by' => current_user()['username'] ?? 'system',
+ 'source_document_id' => (int)($source['id'] ?? 0),
+ 'source_document_code' => $source['code'] ?? null,
+ 'source_document_type' => $source['record_type'] ?? null,
+ 'source_document_label' => $source ? sales_document_label((string)$source['record_type']) : null,
+ 'inventory_effect' => $documentType === 'sales_order' ? 'deducted' : 'none',
+ ];
+}
+
+$errors = [];
+if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+ verify_csrf();
+ $action = (string)($_POST['form_action'] ?? 'create_document');
+
+ if ($action === 'create_document') {
+ $documentType = (string)($_POST['document_type'] ?? 'sales_order');
+ $customerId = (int)($_POST['customer_id'] ?? 0);
+ $branch = trim((string)($_POST['branch'] ?? ''));
+ $productId = (int)($_POST['product_id'] ?? 0);
+ $qty = (float)($_POST['qty'] ?? 0);
+ $notes = trim((string)($_POST['notes'] ?? ''));
+
+ if (!in_array($documentType, ['sales_quote', 'sales_order'], true)) {
+ $errors[] = 'نوع المستند غير صحيح.';
+ }
+
+ $customer = fetch_record('customer', $customerId);
+ $product = fetch_record('product', $productId);
+ validate_sales_input($customer, $product, $branch, $qty, $errors);
+
+ if (!$errors && $customer && $product) {
+ db()->beginTransaction();
+ try {
+ $payload = build_sales_payload($customer, $product, $branch, $qty, $notes, $documentType);
+ $status = $documentType === 'sales_quote' ? 'draft' : 'confirmed';
+ $documentId = create_record(
+ $documentType,
+ sales_document_label($documentType) . ' ' . $customer['title'],
+ next_code(sales_document_prefix($documentType), $documentType),
+ $payload,
+ $status
+ );
+
+ if ($documentType === 'sales_order') {
+ $document = fetch_sales_document_by_id($documentId);
+ if ($document) {
+ $stock = adjust_product_stock($product, -$qty, 'sales_confirm', $document['code'], 'sales_order', $documentId, 'بيع للعميل ' . $customer['title']);
+ $payload = $document['payload_data'];
+ $payload['stock_after'] = $stock['after'];
+ $payload['inventory_effect'] = 'deducted';
+ update_record_payload($documentId, $payload, 'confirmed');
+ }
+ }
+
+ db()->commit();
+ set_flash('success', $documentType === 'sales_quote' ? 'تم إنشاء عرض السعر بنجاح.' : 'تم إنشاء أمر البيع وتحديث المخزون بنجاح.');
+ redirect('sales_orders.php?id=' . $documentId);
+ } catch (Throwable $e) {
+ db()->rollBack();
+ $errors[] = $e instanceof RuntimeException ? 'المخزون غير كافٍ لتأكيد أمر البيع.' : 'تعذر حفظ المستند، حاول مرة أخرى.';
+ }
+ }
+ }
+
+ if ($action === 'convert_document') {
+ $sourceId = (int)($_POST['source_id'] ?? 0);
+ $targetType = (string)($_POST['target_type'] ?? '');
+ $source = fetch_sales_document_by_id($sourceId);
+
+ if (!$source) {
+ $errors[] = 'المستند المصدر غير موجود.';
+ } elseif (!in_array($targetType, sales_document_conversion_targets((string)$source['record_type']), true)) {
+ $errors[] = 'التحويل المطلوب غير مسموح لهذه المرحلة.';
+ } elseif (sales_document_child_exists($sourceId, $targetType)) {
+ $errors[] = 'تم إنشاء هذا المستند مسبقًا من نفس المصدر.';
+ }
+
+ if (!$errors && $source) {
+ $payload = $source['payload_data'];
+ $customer = fetch_record('customer', (int)($payload['customer_id'] ?? 0));
+ $product = fetch_record('product', (int)($payload['product_id'] ?? 0));
+ $branch = (string)($payload['branch'] ?? '');
+ $qty = (float)($payload['qty'] ?? 0);
+ $notes = trim((string)($payload['notes'] ?? ''));
+
+ validate_sales_input($customer, $product, $branch, $qty, $errors);
+
+ if (!$errors && $customer && $product) {
+ db()->beginTransaction();
+ try {
+ $newPayload = build_sales_payload($customer, $product, $branch, $qty, $notes, $targetType, $source);
+ if ($targetType === 'delivery_note') {
+ $newPayload['delivered_at'] = date('Y-m-d H:i');
+ }
+ if ($targetType === 'sales_invoice') {
+ $newPayload['invoiced_at'] = date('Y-m-d H:i');
+ $newPayload['payment_status'] = 'unpaid';
+ }
+
+ $newId = create_record(
+ $targetType,
+ sales_document_label($targetType) . ' ' . $customer['title'],
+ next_code(sales_document_prefix($targetType), $targetType),
+ $newPayload,
+ 'confirmed'
+ );
+
+ if ($targetType === 'sales_order') {
+ $document = fetch_sales_document_by_id($newId);
+ if ($document) {
+ $stock = adjust_product_stock($product, -$qty, 'sales_convert', $document['code'], 'sales_order', $newId, 'تحويل من عرض سعر إلى أمر بيع للعميل ' . $customer['title']);
+ $newPayload = $document['payload_data'];
+ $newPayload['stock_after'] = $stock['after'];
+ $newPayload['inventory_effect'] = 'deducted';
+ update_record_payload($newId, $newPayload, 'confirmed');
+ }
+ }
+
+ db()->commit();
+ set_flash('success', 'تم إنشاء ' . sales_document_label($targetType) . ' بنجاح.');
+ redirect('sales_orders.php?id=' . $newId);
+ } catch (Throwable $e) {
+ db()->rollBack();
+ $errors[] = $e instanceof RuntimeException ? 'المخزون غير كافٍ لإتمام التحويل.' : 'تعذر تنفيذ التحويل، حاول مرة أخرى.';
+ }
+ }
+ }
+ }
+}
+
+$customers = customer_dataset();
+$products = product_dataset();
+$documents = fetch_sales_documents();
+$detail = isset($_GET['id']) ? fetch_sales_document_by_id((int)$_GET['id']) : null;
+$detailChildren = $detail ? sales_document_children((int)$detail['id']) : [];
+$summaryCounts = [];
+foreach (sales_document_record_types() as $type) {
+ $summaryCounts[$type] = record_count($type);
+}
+
+render_header('المبيعات والوثائق', 'إدارة عروض الأسعار وأوامر البيع وأوامر التسليم والفواتير وربطها في مسار واحد قابل للطباعة.', 'orders');
+?>
+
+
عروض الأسعار
= e((string)$summaryCounts['sales_quote']) ?>
مرحلة التسعير قبل خصم المخزون
+
أوامر البيع
= e((string)$summaryCounts['sales_order']) ?>
تؤكد البيع وتخصم المخزون
+
أوامر التسليم
= e((string)$summaryCounts['delivery_note']) ?>
جاهزة للطباعة والتسليم
+
الفواتير
= e((string)$summaryCounts['sales_invoice']) ?>
مرتبطة بالعميل وبالمحاسبة لاحقًا
+
+
+
+
+
+
+
+
+
+
+
+
+
+
نوع المستند
+
+ عرض سعر
+ أمر بيع مباشر
+
+
عرض السعر لا يغير المخزون، بينما أمر البيع يخصم الكمية مباشرة.
+
+
+ العميل
+
+ اختر العميل
+
+ = e($customer['name']) ?> — = e($customer['code']) ?>
+
+
+
+
+ الفرع
+ اختر الفرع بعد العميل
+
+
+
الصنف
+
+ اختر الصنف
+
+ = e($product['name']) ?> — = e($product['sku']) ?>
+
+
+
اختر العميل أولًا لمعرفة قيود الأصناف.
+
+
+
+
المخزون المتاح —
+
الإجمالي قبل الضريبة 0.00 ر.س
+
ضريبة القيمة المضافة 15% 0.00 ر.س
+
الإجمالي النهائي 0.00 ر.س
+
+ ملاحظات
+ إنشاء المستند
+
+
+
+
+
+
+
+
+
+
+ الرقم النوع العميل الصنف الإجمالي المصدر الطباعة
+
+
+
+ = e($document['code']) ?> = e(order_status_label((string)$document['status'])) ?>
+ = e(sales_document_label((string)$document['record_type'])) ?>
+ = e($payload['customer_name'] ?? '') ?>= e($payload['branch'] ?? '') ?>
+ = e($payload['product_name'] ?? '') ?>= e((string)($payload['qty'] ?? 0)) ?> = e($payload['unit'] ?? '') ?>
+ = e(format_money((float)($payload['grand_total'] ?? 0))) ?>
+ = e($payload['source_document_code'] ?? '—') ?>
+ طباعة
+
+
+
+
+
+
+
لا توجد وثائق مبيعات بعد.
+
+
+
+
+
+
+
+
رقم المستند = e($detail['code']) ?>
+
النوع = e(sales_document_label((string)$detail['record_type'])) ?>
+
الحالة = e(order_status_label((string)$detail['status'])) ?>
+
العميل / الفرع = e(($payload['customer_name'] ?? '') . ' — ' . ($payload['branch'] ?? '')) ?>
+
تاريخ الإنشاء = e($payload['created_date'] ?? '') ?>
+
المصدر = e(($payload['source_document_code'] ?? '') ?: 'مباشر') ?>
+
+
+
= e($payload['product_name'] ?? '') ?> = e($payload['sku'] ?? '') ?>
+
الكمية = e((string)($payload['qty'] ?? 0)) ?> = e($payload['unit'] ?? '') ?>
+
سعر الوحدة = e(format_money((float)($payload['unit_price'] ?? 0))) ?>
+
الإجمالي قبل الضريبة = e(format_money((float)($payload['subtotal'] ?? 0))) ?>
+
الضريبة = e(format_money((float)($payload['vat'] ?? 0))) ?>
+
الإجمالي النهائي = e(format_money((float)($payload['grand_total'] ?? 0))) ?>
+
+
المخزون بعد العملية = e((string)$payload['stock_after']) ?>
+
+
+
حالة الدفع = e($payload['payment_status']) ?>
+
+
+
+
+
+
+
+
الوثائق المرتبطة
+
+
+
+
+
= e(sales_document_label((string)$child['record_type'])) ?>
+
= e($child['code']) ?> — = e($childPayload['created_date'] ?? '') ?>
+
+
+
+
+
+
+
+
+
= e($payload['notes']) ?>
+
+
أنشئ مستندًا جديدًا أو اختر واحدًا من الجدول لعرض تفاصيله.
+
+
+
+
+
+
diff --git a/stock_movements.php b/stock_movements.php
new file mode 100644
index 0000000..309dfcc
--- /dev/null
+++ b/stock_movements.php
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+ المرجع الصنف نوع الحركة التغيير قبل / بعد المنشئ
+
+
+
+ = e($payload['reference_code'] ?? $movement['code']) ?>
= e(substr((string)($movement['created_at'] ?? ''), 0, 16)) ?>
+ = e($payload['product_name'] ?? '') ?>= e($payload['sku'] ?? '') ?>
+ = e($payload['movement_type'] ?? '') ?>
+ = e(movement_direction_label($delta)) ?> = e((string)$delta) ?> = e($payload['unit'] ?? '') ?>
+ = e((string)($payload['qty_before'] ?? 0)) ?> → = e((string)($payload['qty_after'] ?? 0)) ?>
+ = e($payload['created_by'] ?? '') ?>
+
+
+
+
+
+
+
لا توجد حركات مخزون بعد. أنشئ أمر شراء أو أمر بيع لتظهر هنا.
+
+
+
diff --git a/suppliers.php b/suppliers.php
new file mode 100644
index 0000000..d0a44db
--- /dev/null
+++ b/suppliers.php
@@ -0,0 +1,108 @@
+ $phone,
+ 'email' => $email,
+ 'supplied_skus' => $suppliedSkus,
+ 'notes' => $notes,
+ ]);
+ set_flash('success', 'تمت إضافة المورد بنجاح.');
+ redirect('suppliers.php');
+ }
+}
+
+$suppliers = fetch_records('supplier');
+$detail = isset($_GET['id']) ? fetch_record('supplier', (int)$_GET['id']) : null;
+render_header('إدارة الموردين', 'إضافة الموردين وربط الأصناف التي يوردونها مع قسم المشتريات.', 'suppliers');
+?>
+
+
+
+
+
+
+
+
+
+
+ اسم المورد
+
+
+
+
+
الأصناف الموردة
+
+
افصل الـ SKU بفاصلة.
+
+
+ ملاحظات
+
+
+ حفظ المورد
+
+
+
+
+
+
+
+
+ المورد التواصل الأصناف تفاصيل
+
+
+
+ = e($supplier['title']) ?>
= e($supplier['code']) ?>
+ = e($payload['phone'] ?? '') ?>= e($payload['email'] ?? '') ?>
+ = e(implode('، ', $payload['supplied_skus'] ?? [])) ?>
+ عرض
+
+
+
+
+
+
+
+
+
+
+
اسم المورد = e($detail['title']) ?>
+
الرقم = e($detail['code']) ?>
+
الهاتف = e($payload['phone'] ?? '') ?>
+
البريد = e($payload['email'] ?? '') ?>
+
+
الأصناف الموردة
= e(implode('، ', $payload['supplied_skus'] ?? [])) ?: '—' ?>
+
= e($payload['notes'] ?? '') ?>
+
+
اختر موردًا من الجدول لعرض التفاصيل.
+
+
+
+
+