invoice system

مشروع تطبيقي: نظام فواتير متعدد الدول للسعودية والإمارات ومصر

|

بناء نظام فواتير برمجي خطوة بخطوة يعرض العملات والتواريخ والنسب المئوية تلقائياً للسعودية والإمارات ومصر باستخدام جافا سكريبت و Intl API.

عدد الكلمات: ~٢٤٠٠ • مدة القراءة: ١٢ دقيقة

مشروعُ تطبيقي: نظام فواتيرٍ متعدد الدول

نبني خطوةً بخطوة — ثم نجمع الكود كاملاً في النهاية


ملاحظة للقارئ: هذا المقال هو المشروع التطبيقي الختامي للسلسلة. يمكن قراءته مستقلاً، لكنه يستفيد من المفاهيم التي بنيناها في المقالات الثلاثة السابقة — الأرقام والخطوط، وواجهة Intl، ومعضلات قواعد البيانات.

في هذا المقال من منصة ذي يزن، نصل إلى لحظة الاختبار الحقيقي: هل ما تعلمناه في المقالات الثلاثة السابقة يتحول إلى نظامٍ عمليٍ قابل للاستخدام؟ الجواب نعم، وسنثبت ذلك بكودٍ حقيقي، خطوةً بخطوة.

المشروع: نظام فواتيرٍ بسيطٌ يعرض الفاتورة ذاتها بثلاثة تنسيقات مختلفة: المملكة العربية السعودية، الإمارات العربية المتحدة، ومصر، وذلك بضغطة زرٍ واحدة. العملات، والتواريخ بنظامَيها، والنسب المئوية، وموضع رمز العملة، كل شيءٍ يتكيّف تلقائياً.

arabic english document translation professional

الخطوة الأولى: تعريف بيانات الدول (Config Layer)

أول قرارٍ معماريٍ في أيّ نظام توطين: افصل إعدادات كل منطقةٍ عن منطق العرض. هذا يعني أن إضافة دولةٍ جديدةٍ لاحقاً لن تتطلب تعديل أي كود عرضٍ، بل فقط إضافة كائن إعدادٍ جديد.

// ── الخطوة ١: إعدادات الدول ──────────────────────────────
const COUNTRY_CONFIG = {
  'SA': {
    label:         'المملكة العربية السعودية',
    locale:        'ar-SA',
    currency:      'SAR',
    vatRate:       0.15,          // ١٥٪ ضريبة القيمة المضافة
    showHijri:     true,          // عرض التاريخ الهجري
    decimalDigits: 2,             // عدد الخانات العشرية للعملة
  },
  'AE': {
    label:         'الإمارات العربية المتحدة',
    locale:        'ar-AE',
    currency:      'AED',
    vatRate:       0.05,          // ٥٪ ضريبة القيمة المضافة
    showHijri:     false,
    decimalDigits: 2,
  },
  'EG': {
    label:         'مصر',
    locale:        'ar-EG',
    currency:      'EGP',
    vatRate:       0.14,          // ١٤٪ ضريبة القيمة المضافة
    showHijri:     false,
    decimalDigits: 2,
  },
};

لاحظ ثلاثة قراراتٍ مضمّنةٍ هنا: نسب الضريبة مخزّنةٌ كقيمٍ نسبية (0.15) لتتوافق مباشرةً مع Intl، ورمز العملة بمعيار ISO 4217، وخيار showHijri منفصلٌ لأن السعودية وحدها تعرض التاريخ الهجري افتراضياً في هذا النظام.

الخطوة الثانية: بناء طبقة التنسيق (Formatters)

بدلاً من إنشاء كائنات Intl في كل مكانٍ نحتاجها، نبني دالةً واحدةً تُنشئها مرةً واحدة لكل دولةٍ وتُعيد كائناتٍ جاهزةٍ للاستخدام، وهذا يحل مشكلة الأداء التي ناقشناها في المقال الثاني:

// ── الخطوة ٢: بناء طبقة التنسيق ─────────────────────────
function buildFormatters(countryCode) {
  const config = COUNTRY_CONFIG[countryCode];
  if (!config) throw new Error(`Unknown country: ${countryCode}`);

  const { locale, currency, decimalDigits } = config;

  return {
    // تنسيق المبالغ المالية
    currency: new Intl.NumberFormat(locale, {
      style:                 'currency',
      currency:              currency,
      minimumFractionDigits: decimalDigits,
      maximumFractionDigits: decimalDigits,
    }),

    // تنسيق النسب المئوية
    percent: new Intl.NumberFormat(locale, {
      style:                 'percent',
      minimumFractionDigits: 0,
      maximumFractionDigits: 1,
    }),

    // تنسيق الأرقام العادية (للكميات)
    number: new Intl.NumberFormat(locale, {
      minimumFractionDigits: 0,
      maximumFractionDigits: 2,
    }),

    // تنسيق التاريخ الميلادي
    dateGregorian: new Intl.DateTimeFormat(`${locale}-u-ca-gregory`, {
      day:   '2-digit',
      month: 'long',
      year:  'numeric',
    }),

    // تنسيق التاريخ الهجري (للسعودية)
    dateHijri: new Intl.DateTimeFormat(`${locale}-u-ca-islamic`, {
      day:   '2-digit',
      month: 'long',
      year:  'numeric',
    }),
  };
}

الخطوة الثالثة: حساب الفاتورة (Invoice Calculator)

الآن نبني الدالة التي تأخذ بنود الفاتورة الخام وتُنتج المجاميع المحسوبة، ومن المهم أن تعمل هذه الدالة على الأرقام الخام فقط، دون أي تنسيق:

// ── الخطوة ٣: حساب مجاميع الفاتورة ──────────────────────
function calculateInvoice(items, vatRate) {
  // الجمع على الأرقام الخام — لا تنسيق هنا أبداً
  const subtotal = items.reduce(
    (sum, item) => sum + item.quantity * item.unitPrice,
    0
  );

  // حساب الضريبة على المجموع الكلي (قرار موثَّق)
  const vatAmount = Math.round(subtotal * vatRate * 100) / 100;

  const total = subtotal + vatAmount;

  return { subtotal, vatAmount, total };
}

لاحظ تعليق “لا تنسيق هنا أبداً”.. هذا التذكير الصريح في الكود يمنع زميلاً مستقبلياً من إدخال Intl.format() داخل دالة الحساب، وهو خطأ شائع يصعب تتبعه لاحقاً.

الخطوة الرابعة: تنسيق التاريخ الثنائي

السعودية تحتاج عرض التاريخين معاً في الفاتورة الرسمية: الهجري والميلادي. بنينا نسخةً بسيطةً من هذه الدالة في المقال الثاني، وهنا نُطوّرها لتتكامل مع نظام الإعدادات:

// ── الخطوة ٤: تنسيق التاريخ بحسب إعداد الدولة ───────────
function formatInvoiceDate(date, countryCode, formatters) {
  const config = COUNTRY_CONFIG[countryCode];
  const gregorian = formatters.dateGregorian.format(date);

  if (!config.showHijri) {
    // الإمارات ومصر: التاريخ الميلادي فقط
    return gregorian;
  }

  // السعودية: الهجري أولاً ثم الميلادي
  const hijri = formatters.dateHijri.format(date);
  return `${hijri} — ${gregorian}`;
}

// أمثلة على المخرجات لتاريخ ١٥ مايو ٢٠٢٦:
// SA → 'ذو القعدة ١٧، ١٤٤٧ هـ — ١٥ مايو ٢٠٢٦'
// AE → '١٥ مايو ٢٠٢٦'
// EG → '١٥ مايو ٢٠٢٦'

الخطوة الخامسة: بناء HTML الفاتورة

الآن نجمع كل ما بنيناه في دالةٍ تُنشئ HTML الفاتورة الكاملة. الدالة تأخذ بيانات الفاتورة ورمز الدولة، وتُنتج HTML جاهزاً للعرض/ وسنعرض كيف تظهر الفاتورة أسفل صندوق الكود:

// ── الخطوة ٥: بناء HTML الفاتورة ────────────────────────
function buildInvoiceHTML(invoiceData, countryCode) {
  const config     = COUNTRY_CONFIG[countryCode];
  const formatters = buildFormatters(countryCode);
  const { subtotal, vatAmount, total } =
    calculateInvoice(invoiceData.items, config.vatRate);

  const dateStr = formatInvoiceDate(
    invoiceData.date, countryCode, formatters
  );

  // بناء صفوف بنود الفاتورة
  const itemRows = invoiceData.items.map(item => {
    const lineTotal = item.quantity * item.unitPrice;
    return `
      <tr>
        <td style="padding:10px; border:1px solid #ddd;">
          ${item.name}
        </td>
        <td style="padding:10px; border:1px solid #ddd;
                   text-align:center; font-variant-numeric:tabular-nums;">
          ${formatters.number.format(item.quantity)}
        </td>
        <td style="padding:10px; border:1px solid #ddd;
                   text-align:end; font-variant-numeric:tabular-nums;">
          ${formatters.currency.format(item.unitPrice)}
        </td>
        <td style="padding:10px; border:1px solid #ddd;
                   text-align:end; font-variant-numeric:tabular-nums;">
          ${formatters.currency.format(lineTotal)}
        </td>
      </tr>`;
  }).join('');

  // بناء HTML الكامل
  return `
    <div dir="rtl" style="font-family:'Amiri',serif; max-width:700px;
         margin:0 auto; padding:30px; border:1px solid #ddd; border-radius:8px;">

      <div style="display:flex; justify-content:space-between;
                  align-items:flex-start; margin-bottom:30px;">
        <div>
          <h2 style="color:#c0392b; margin:0;">فاتورة ضريبية</h2>
          <p style="color:#666; margin:5px 0; font-size:0.9em;">
            رقم الفاتورة: ${invoiceData.number}
          </p>
          <p style="color:#666; margin:5px 0; font-size:0.9em;">
            التاريخ: ${dateStr}
          </p>
        </div>
        <div style="text-align:left;">
          <p style="font-weight:bold; margin:0;">${config.label}</p>
          <p style="color:#666; margin:5px 0; font-size:0.9em;">
            ضريبة القيمة المضافة:
            ${formatters.percent.format(config.vatRate)}
          </p>
        </div>
      </div>

      <div style="background:#f9f9f9; padding:15px; border-radius:4px;
                  margin-bottom:25px;">
        <p style="margin:0;">
          <strong>العميل:</strong> ${invoiceData.client.name}
        </p>
        <p style="margin:5px 0; color:#666; font-size:0.9em;">
          ${invoiceData.client.address}
        </p>
      </div>

      <div style="overflow-x:auto; margin-bottom:25px;">
        <table style="width:100%; border-collapse:collapse; font-size:0.95em;">
          <thead>
            <tr style="background:#1a3a5c; color:white;">
              <th style="padding:10px; border:1px solid #ddd; text-align:right;">
                البند
              </th>
              <th style="padding:10px; border:1px solid #ddd;">الكمية</th>
              <th style="padding:10px; border:1px solid #ddd;">سعر الوحدة</th>
              <th style="padding:10px; border:1px solid #ddd;">الإجمالي</th>
            </tr>
          </thead>
          <tbody>${itemRows}</tbody>
        </table>
      </div>

      <div style="width:280px; margin-right:auto; margin-left:0;">
        <div style="display:flex; justify-content:space-between;
                    padding:8px 0; border-bottom:1px solid #eee;">
          <span>المجموع قبل الضريبة</span>
          <span style="font-variant-numeric:tabular-nums;">
            ${formatters.currency.format(subtotal)}
          </span>
        </div>
        <div style="display:flex; justify-content:space-between;
                    padding:8px 0; border-bottom:1px solid #eee; color:#666;">
          <span>
            ضريبة القيمة المضافة
            (${formatters.percent.format(config.vatRate)})
          </span>
          <span style="font-variant-numeric:tabular-nums;">
            ${formatters.currency.format(vatAmount)}
          </span>
        </div>
        <div style="display:flex; justify-content:space-between;
                    padding:10px 0; font-weight:bold; font-size:1.1em;
                    color:#c0392b; border-top:2px solid #c0392b;">
          <span>الإجمالي النهائي</span>
          <span style="font-variant-numeric:tabular-nums;">
            ${formatters.currency.format(total)}
          </span>
        </div>
      </div>

      <p style="text-align:center; color:#999; font-size:0.8em;
                margin-top:30px; border-top:1px solid #eee; padding-top:15px;">
        تم إنشاء هذه الفاتورة آلياً بواسطة نظام التوطين
      </p>
    </div>`;
}

🔍
نموذج توضيحي — المملكة العربية السعودية
هذا ما يُنتجه الكود أعلاه عند تنفيذه

فاتورة ضريبية

رقم الفاتورة: INV-2026-0042

التاريخ: ١٧ ذو القعدة ١٤٤٧ هـ — ١٥ مايو ٢٠٢٦

المملكة العربية السعودية

ضريبة القيمة المضافة: ١٥٪

العميل: شركة المستقبل للتقنية

المنطقة المالية، الطابق الثاني عشر

البند الكمية سعر الوحدة الإجمالي
تصميم واجهة المستخدم ١ ر.س ٤٬٥٠٠٫٠٠ ر.س ٤٬٥٠٠٫٠٠
تطوير الواجهة الأمامية ١ ر.س ٨٬٢٠٠٫٠٠ ر.س ٨٬٢٠٠٫٠٠
جلسات مراجعة ومتابعة ٤ ر.س ٧٥٠٫٠٠ ر.س ٣٬٠٠٠٫٠٠
توثيق تقني ١ ر.س ١٬٢٠٠٫٠٠ ر.س ١٬٢٠٠٫٠٠

المجموع قبل الضريبة
ر.س ١٦٬٩٠٠٫٠٠
ضريبة القيمة المضافة (١٥٪)
ر.س ٢٬٥٣٥٫٠٠
الإجمالي النهائي
ر.س ١٩٬٤٣٥٫٠٠

تم إنشاء هذه الفاتورة آلياً بواسطة نظام التوطين

الشكل ١ — مخرجات buildInvoiceHTML(invoiceData, 'SA') بعد التنفيذ: التاريخ مزدوج (هجري + ميلادي)، الرمز قبل الرقم، الأرقام هندية، ضريبة ١٥٪.

الخطوة السادسة: تشغيل النظام

الآن نُعرّف بيانات الفاتورة التجريبية ونُشغّل النظام للدول الثلاث:

// ── الخطوة ٦: بيانات الفاتورة والتشغيل ──────────────────

// بيانات الفاتورة — مخزّنة كأرقام خام بلا تنسيق
const invoiceData = {
  number: 'INV-2026-0042',
  date:   new Date(2026, 4, 15),   // ١٥ مايو ٢٠٢٦
  client: {
    name:    'شركة المستقبل للتقنية',
    address: 'المنطقة المالية، الطابق الثاني عشر',
  },
  items: [
    { name: 'تصميم واجهة المستخدم',  quantity: 1,  unitPrice: 4500.00 },
    { name: 'تطوير الواجهة الأمامية', quantity: 1,  unitPrice: 8200.00 },
    { name: 'جلسات مراجعة ومتابعة',  quantity: 4,  unitPrice:  750.00 },
    { name: 'توثيق تقني',             quantity: 1,  unitPrice: 1200.00 },
  ],
};

// تشغيل النظام للدول الثلاث
const countries = ['SA', 'AE', 'EG'];

countries.forEach(code => {
  const html = buildInvoiceHTML(invoiceData, code);
  console.log(`\n=== فاتورة ${COUNTRY_CONFIG[code].label} ===`);
  console.log(html);
  // في تطبيق حقيقي: document.getElementById(`invoice-${code}`).innerHTML = html;
});

بسطرين فقط في الاستخدام النهائي، النظام يُنتج ثلاث فواتيرٍ متمايزةٍ تماماً. إضافة الكويت أو البحرين أو المغرب لاحقاً تعني إضافة كائنٍ واحدٍ في COUNTRY_CONFIG ، ولا شيء آخر.

هذا هو معنى التصميم القابل للتوسع: لا تُبرمج لثلاث دول، بل تُبرمج لأي عددٍ من الدول.

الخطوة السابعة: اختبارٌ سريعٌ للمخرجات

قبل أن نجمع الكود الكامل، نتحقق من أن المخرجات صحيحةً لكل دولة. الجدول التالي يلخص ما يجب أن تراه لبندٍ واحدٍ بقيمة ١٠٠٠ وحدة نقدية:

الحقل السعودية (ar-SA) الإمارات (ar-AE) مصر (ar-EG)
المبلغ ر.س ١٬٠٠٠٫٠٠ د.إ ١٬٠٠٠٫٠٠ ١٬٠٠٠٫٠٠ ج.م
نسبة الضريبة ١٥٪ ٥٪ ١٤٪
قيمة الضريبة ر.س ١٥٠٫٠٠ د.إ ٥٠٫٠٠ ١٤٠٫٠٠ ج.م
الإجمالي ر.س ١٬١٥٠٫٠٠ د.إ ١٬٠٥٠٫٠٠ ١٬١٤٠٫٠٠ ج.م
التاريخ هجري + ميلادي ميلادي فقط ميلادي فقط
موضع رمز العملة قبل الرقم قبل الرقم بعد الرقم

لاحظ كيف أن مصر وحدها تضع رمز العملة بعد الرقم، وهذا ليس قراراً برمجياً اتخذناه نحن، بل هو ما تُمليه بيانات CLDR لـ ar-EG وتطبّقه Intl تلقائياً.

invoice system arabic


الكود الكامل — جاهزٌ للنسخ والتشغيل

هذا هو النظام كاملاً في ملف جافا سكريبت واحد. يعمل في المتصفح وفي Node.js دون أي مكتباتٍ خارجية:

// ════════════════════════════════════════════════════════
// نظام فواتير متعدد الدول — دليل معايرة البيانات المالية
// منصة ذي يزن | zyyazan.sy
// ════════════════════════════════════════════════════════

// ── ١. إعدادات الدول ─────────────────────────────────────
const COUNTRY_CONFIG = {
  'SA': {
    label:         'المملكة العربية السعودية',
    locale:        'ar-SA',
    currency:      'SAR',
    vatRate:       0.15,
    showHijri:     true,
    decimalDigits: 2,
  },
  'AE': {
    label:         'الإمارات العربية المتحدة',
    locale:        'ar-AE',
    currency:      'AED',
    vatRate:       0.05,
    showHijri:     false,
    decimalDigits: 2,
  },
  'EG': {
    label:         'مصر',
    locale:        'ar-EG',
    currency:      'EGP',
    vatRate:       0.14,
    showHijri:     false,
    decimalDigits: 2,
  },
};

// ── ٢. طبقة التنسيق ──────────────────────────────────────
function buildFormatters(countryCode) {
  const config = COUNTRY_CONFIG[countryCode];
  if (!config) throw new Error(`Unknown country: ${countryCode}`);

  const { locale, currency, decimalDigits } = config;

  return {
    currency: new Intl.NumberFormat(locale, {
      style:                 'currency',
      currency:              currency,
      minimumFractionDigits: decimalDigits,
      maximumFractionDigits: decimalDigits,
    }),
    percent: new Intl.NumberFormat(locale, {
      style:                 'percent',
      minimumFractionDigits: 0,
      maximumFractionDigits: 1,
    }),
    number: new Intl.NumberFormat(locale, {
      minimumFractionDigits: 0,
      maximumFractionDigits: 2,
    }),
    dateGregorian: new Intl.DateTimeFormat(`${locale}-u-ca-gregory`, {
      day: '2-digit', month: 'long', year: 'numeric',
    }),
    dateHijri: new Intl.DateTimeFormat(`${locale}-u-ca-islamic`, {
      day: '2-digit', month: 'long', year: 'numeric',
    }),
  };
}

// ── ٣. حساب مجاميع الفاتورة ──────────────────────────────
function calculateInvoice(items, vatRate) {
  const subtotal  = items.reduce(
    (sum, item) => sum + item.quantity * item.unitPrice, 0
  );
  const vatAmount = Math.round(subtotal * vatRate * 100) / 100;
  const total     = subtotal + vatAmount;
  return { subtotal, vatAmount, total };
}

// ── ٤. تنسيق التاريخ ─────────────────────────────────────
function formatInvoiceDate(date, countryCode, formatters) {
  const gregorian = formatters.dateGregorian.format(date);
  if (!COUNTRY_CONFIG[countryCode].showHijri) return gregorian;
  const hijri = formatters.dateHijri.format(date);
  return `${hijri} — ${gregorian}`;
}

// ── ٥. بناء HTML الفاتورة ─────────────────────────────────
function buildInvoiceHTML(invoiceData, countryCode) {
  const config     = COUNTRY_CONFIG[countryCode];
  const formatters = buildFormatters(countryCode);
  const { subtotal, vatAmount, total } =
    calculateInvoice(invoiceData.items, config.vatRate);
  const dateStr = formatInvoiceDate(
    invoiceData.date, countryCode, formatters
  );

  const itemRows = invoiceData.items.map(item => {
    const lineTotal = item.quantity * item.unitPrice;
    return `
      <tr>
        <td style="padding:10px;border:1px solid #ddd;">
          ${item.name}
        </td>
        <td style="padding:10px;border:1px solid #ddd;
                   text-align:center;font-variant-numeric:tabular-nums;">
          ${formatters.number.format(item.quantity)}
        </td>
        <td style="padding:10px;border:1px solid #ddd;
                   text-align:end;font-variant-numeric:tabular-nums;">
          ${formatters.currency.format(item.unitPrice)}
        </td>
        <td style="padding:10px;border:1px solid #ddd;
                   text-align:end;font-variant-numeric:tabular-nums;">
          ${formatters.currency.format(lineTotal)}
        </td>
      </tr>`;
  }).join('');

  return `
    <div dir="rtl" style="font-family:'Amiri',serif;max-width:700px;
         margin:0 auto;padding:30px;border:1px solid #ddd;border-radius:8px;">

      <div style="display:flex;justify-content:space-between;
                  align-items:flex-start;margin-bottom:30px;">
        <div>
          <h2 style="color:#c0392b;margin:0;">فاتورة ضريبية</h2>
          <p style="color:#666;margin:5px 0;font-size:0.9em;">
            رقم الفاتورة: ${invoiceData.number}
          </p>
          <p style="color:#666;margin:5px 0;font-size:0.9em;">
            التاريخ: ${dateStr}
          </p>
        </div>
        <div style="text-align:left;">
          <p style="font-weight:bold;margin:0;">${config.label}</p>
          <p style="color:#666;margin:5px 0;font-size:0.9em;">
            ضريبة القيمة المضافة:
            ${formatters.percent.format(config.vatRate)}
          </p>
        </div>
      </div>

      <div style="background:#f9f9f9;padding:15px;border-radius:4px;
                  margin-bottom:25px;">
        <p style="margin:0;">
          <strong>العميل:</strong> ${invoiceData.client.name}
        </p>
        <p style="margin:5px 0;color:#666;font-size:0.9em;">
          ${invoiceData.client.address}
        </p>
      </div>

      <div style="overflow-x:auto;margin-bottom:25px;">
        <table style="width:100%;border-collapse:collapse;font-size:0.95em;">
          <thead>
            <tr style="background:#1a3a5c;color:white;">
              <th style="padding:10px;border:1px solid #ddd;text-align:right;">
                البند
              </th>
              <th style="padding:10px;border:1px solid #ddd;">الكمية</th>
              <th style="padding:10px;border:1px solid #ddd;">سعر الوحدة</th>
              <th style="padding:10px;border:1px solid #ddd;">الإجمالي</th>
            </tr>
          </thead>
          <tbody>${itemRows}</tbody>
        </table>
      </div>

      <div style="width:280px;margin-right:auto;margin-left:0;">
        <div style="display:flex;justify-content:space-between;
                    padding:8px 0;border-bottom:1px solid #eee;">
          <span>المجموع قبل الضريبة</span>
          <span style="font-variant-numeric:tabular-nums;">
            ${formatters.currency.format(subtotal)}
          </span>
        </div>
        <div style="display:flex;justify-content:space-between;
                    padding:8px 0;border-bottom:1px solid #eee;color:#666;">
          <span>
            ضريبة القيمة المضافة
            (${formatters.percent.format(config.vatRate)})
          </span>
          <span style="font-variant-numeric:tabular-nums;">
            ${formatters.currency.format(vatAmount)}
          </span>
        </div>
        <div style="display:flex;justify-content:space-between;
                    padding:10px 0;font-weight:bold;font-size:1.1em;
                    color:#c0392b;border-top:2px solid #c0392b;">
          <span>الإجمالي النهائي</span>
          <span style="font-variant-numeric:tabular-nums;">
            ${formatters.currency.format(total)}
          </span>
        </div>
      </div>

      <p style="text-align:center;color:#999;font-size:0.8em;
                margin-top:30px;border-top:1px solid #eee;padding-top:15px;">
        تم إنشاء هذه الفاتورة آلياً بواسطة نظام التوطين
      </p>
    </div>`;
}

// ── ٦. بيانات الفاتورة التجريبية والتشغيل ────────────────
const invoiceData = {
  number: 'INV-2026-0042',
  date:   new Date(2026, 4, 15),
  client: {
    name:    'شركة المستقبل للتقنية',
    address: 'المنطقة المالية، الطابق الثاني عشر',
  },
  items: [
    { name: 'تصميم واجهة المستخدم',   quantity: 1, unitPrice: 4500.00 },
    { name: 'تطوير الواجهة الأمامية', quantity: 1, unitPrice: 8200.00 },
    { name: 'جلسات مراجعة ومتابعة',   quantity: 4, unitPrice:  750.00 },
    { name: 'توثيق تقني',              quantity: 1, unitPrice: 1200.00 },
  ],
};

['SA', 'AE', 'EG'].forEach(code => {
  const container = document.getElementById(`invoice-${code}`);
  if (container) {
    container.innerHTML = buildInvoiceHTML(invoiceData, code);
  }
});

// ════════════════════════════════════════════════════════
// نهاية نظام الفواتير — منصة ذي يزن | zyyazan.sy
// ════════════════════════════════════════════════════════

ما الذي يمكن إضافته لاحقاً؟

هذا النظام عمدٌ إلى البساطة لتوضيح المفاهيم. في مشروعٍ إنتاجيٍ حقيقي، الخطوات التالية المنطقية هي:

  • تحويل العملات: إضافة API لأسعار الصرف الفعلية وتحويل المبالغ بين العملات قبل العرض
  • التحقق من صحة البيانات: التحقق من أن المبالغ أرقامٌ موجبةٌ وأن رموز العملات موجودةٌ في ISO 4217
  • تصدير PDF: تحويل HTML الفاتورة إلى PDF بمكتباتٍ مثل puppeteer أو jsPDF
  • دعم عملات الكويت والبحرين: إضافة كائنَي إعداد جديدَين في COUNTRY_CONFIG ، ولا شيء آخر

خلاصة السلسلة

في أربعة مقالات، قطعنا رحلةً كاملة: من الفرق بين نقطتَي ترميزٍ في جدول يونيكود، إلى نظام فواتيرٍ جاهزٍ للإنتاج يخدم ثلاث دولٍ بضغطة زر.

المبدأ الجامع للسلسلة كلها بسيط: افصل التخزين عن العرض، وثق قراراتك، ودع المعايير الدولية تتكفل بالباقي. واجهة Intl ليست حيلةً برمجية، إنما هي ثمرة عمل مئات المختصين في بيانات CLDR الذين وثّقوا أعراف كل ثقافةٍ على وجه الأرض. استثمار هذا العمل هو الخيار الأذكى لأي مطورٍ يبني لجمهورٍ عربيٍ متعدد.

وإن كنت تتساءل كيف يرى الحاسوب النص العربي أصلاً قبل أن يُنسّق أي رقم، فتلك قصةٌ أخرى رويناها في سلسلتنا السابقة حول منطق يونيكود والنصوص الهجينة.


المراجع والمصادر:

  1. توثيق MDN — Intl.NumberFormat: developer.mozilla.org
  2. توثيق MDN — Intl.DateTimeFormat: developer.mozilla.org
  3. مستودع CLDR — بيانات المناطق الجغرافية: cldr.unicode.org
  4. لوائح ضريبة القيمة المضافة — السعودية: zatca.gov.sa
  5. لوائح ضريبة القيمة المضافة — الإمارات: tax.gov.ae
  6. معيار ISO 4217 لرموز العملات: iso.org
منصة ذي يزن © ٢٠٢٦

سلاسل التوطين

دليل معايرة البيانات المالية — ٤ مقالات

المقالة 1
١ / ٤

الأرقام العربية والهندية والخطوط: ما الفرق ولماذا يهمك برمجياً؟

دراسة الفروقات الهيكلية والبرمجية بين أنظمة تمثيل الأرقام والخطوط في الأنظمة والمنصات الرقمية.

المقالة 2
٢ / ٤

توطين الأرقام والتواريخ بـ Intl.NumberFormat و Intl.DateTimeFormat

استخدام الأدوات المعيارية لتوطين التواريخ وتنسيقات الأرقام ديناميكياً لتلائم تفضيلات المستخدم المحلي.

المقالة 3
٣ / ٤

معضلات الجداول والعملات والنسب المئوية: ما الذي يحدث خلف الشاشة؟

كواليس معالجة قواعد البيانات والواجهات للحقول المالية الحساسة، وحسابات النسب، واستعراض العملات.

المقالة 4
٤ / ٤

مشروع تطبيقي: نظام فواتير متعدد الدول للسعودية والإمارات ومصر

تطبيق عملي لبناء معايير المحاسبة والفوترة المحلية وتنسيق المخرجات لأسواق عربية متعددة بذكاء.

سلسلة دليل معايرة البيانات المالية — ٤ مقالات  |  منصة ذي يزن © ٢٠٢٦

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *