${bodyHtml}
`; const renderPdfBlob = async (html, filename) => { const response = await fetch('/api/pdf/render', { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ html, filename }), }); if (!response.ok) { const payload = await response.json().catch(() => null); throw new Error((payload && payload.message) || 'تعذر توليد ملف PDF.'); } return response.blob(); }; const downloadPdfHtml = async (html, filename) => { const blob = await renderPdfBlob(html, filename); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = filename || 'contract.pdf'; document.body.appendChild(link); link.click(); link.remove(); setTimeout(() => URL.revokeObjectURL(url), 1000); }; const printContract = async (data, options = {}) => { const GO_GREEN = '#397D54'; const GO_COMPANY_AR = 'مؤسسة قرين أوشن للمقاولات'; const GO_COMPANY_EN = 'GREEN OCEAN CONTRACTING'; const GO_CR = '2031113368'; const GO_PHONE = '0135877002'; const GO_WEBSITE = 'greenocean.sa'; const baseClausesList = Array.isArray(data.clauses) && data.clauses.length ? data.clauses.filter(Boolean) : defaultContractClauses; const paymentsList = (Array.isArray(data.payments) && data.payments.length) ? data.payments.map((payment, index) => normalizePaymentRow(payment, index)) : defaultPaymentSchedule.map((payment, index) => normalizePaymentRow({ ...payment, note: payment.description }, index)); const selectedFeaturesList = Array.isArray(data.selectedFeatures) ? data.selectedFeatures.map((feature) => normalizeTemplateFeature(feature)).filter(Boolean) : []; const normalizedBaseClauses = baseClausesList.map((c) => normalizeClauseLine(c)); const featureClauseLines = selectedFeaturesList .map((feature) => ({ name: String(feature && feature.name ? feature.name : '').trim(), clause: String(feature && feature.clause ? feature.clause : '').trim() })) .filter((fc) => fc.clause && !normalizedBaseClauses.includes(normalizeClauseLine(fc.clause))); const clausesList = [...baseClausesList]; const selectedFeaturesTotal = selectedFeaturesList.reduce((sum, feature) => sum + Number(feature.price || 0), 0); const paidTotal = paymentsList.reduce((sum, item) => sum + Number(item && item.amount ? item.amount : 0), 0); const totalAmount = Number(data.totalAmount || 0) || paidTotal; const remaining = Math.max(0, totalAmount - paidTotal); const contractNoText = formatSerial(data.contractNo || currentContractNo); const contractDateText = String(data.contractDate || '').trim(); const contractTypeText = stripMaterialModeSuffix(String(data.contractType || '').trim()); const materialMode = normalizeMaterialMode(data.materialMode, data.contractType); const materialLabel = materialModeLabel(materialMode); const contractTypeDisplayText = contractTypeText ? `${contractTypeText} [${materialLabel}]` : ''; let weekdayText = ''; let longDateText = contractDateText; const dateObj = contractDateText ? new Date(contractDateText) : null; if (dateObj && !Number.isNaN(dateObj.getTime())) { try { weekdayText = new Intl.DateTimeFormat('ar-SA-u-ca-gregory', { weekday: 'long' }).format(dateObj); } catch { weekdayText = ''; } try { longDateText = new Intl.DateTimeFormat('ar-SA-u-ca-gregory', { year: 'numeric', month: 'long', day: 'numeric' }).format(dateObj); } catch { longDateText = contractDateText; } } // Render a key/value cell only when the value exists (skip empty site fields). const infoCell = (label, value) => { const v = String(value == null ? '' : value).trim(); if (!v) return ''; return `
${escapeHtml(label)}
${escapeHtml(v)}
`; }; const infoLine = (label, value) => { const v = String(value == null ? '' : value).trim(); if (!v) return ''; return `
${escapeHtml(label)}${escapeHtml(v)}
`; }; const projectCells = [ infoCell('نوع العقد', contractTypeDisplayText), infoCell('رقم رخصة البناء', data.buildingLicenseNumber), infoCell('المساحة', data.landArea ? `${String(data.landArea).trim()} م²` : ''), infoCell('المخطط', data.planName), infoCell('رقم المخطط', data.planNumber), infoCell('رقم القطعة / الأرض', data.landNumber), ].filter(Boolean).join(''); const partyTwoName = String(data.clientName || '').trim(); const partyTwoLines = [ infoLine('رقم الهوية', data.identityNumber), infoLine('رقم الجوال', data.phone || data.clientPhone), ].filter(Boolean).join(''); const featuresHtml = selectedFeaturesList.length ? `
${selectedFeaturesList.map((feature) => `
${escapeHtml(String(feature.name || ''))}
`).join('')}
` : ''; const clausesRows = clausesList.map((item) => `
  • ${escapeHtml(normalizeClauseLine(item))}
  • `).join(''); const paymentRows = paymentsList.map((p, idx) => ` ${idx + 1} ${escapeHtml(String(idx === (paymentsList.length - 1) && !(p.note || '').trim() ? 'الدفعة الأخيرة' : (p.note || '-')))} ${moneyHtml(p.amount || 0)} `).join(''); const totalsBoxes = [ selectedFeaturesList.length ? `
    إجمالي المزايا
    ${moneyHtml(selectedFeaturesTotal)}
    ` : '', `
    إجمالي العقد
    ${moneyHtml(totalAmount)}
    `, `
    المدفوع
    ${moneyHtml(paidTotal)}
    `, `
    المتبقي
    ${moneyHtml(remaining)}
    `, ].filter(Boolean).join(''); const headerHtml = `
    قرينأوشنللمقاولات
    GREENOCEANCONTRACTING
    نوع العقد${escapeHtml(contractTypeDisplayText || '-')}
    رقم العقد${escapeHtml(contractNoText)}
    التاريخ${escapeHtml(contractDateText || '-')}
    `; const footerHtml = ` `; const docHtml = `عقد ${escapeHtml(GO_COMPANY_AR)} ${footerHtml}
    ${headerHtml}
    تمهيد
    بسم الله الرحمن الرحيم
    الحمد لله رب العالمين، وصلى الله على عبده ورسوله الكريم، وعلى آله الطاهرين. ${weekdayText || longDateText ? `بعونه تعالى ${weekdayText ? `في يوم ${escapeHtml(weekdayText)} ` : ''}${longDateText ? `الموافق ${escapeHtml(longDateText)} ` : ''}تم الاتفاق بين الطرفين:` : 'تم الاتفاق بين الطرفين:'}

    الطرف الأول${escapeHtml(GO_COMPANY_AR)}

    السجل التجاري${escapeHtml(GO_CR)}
    رقم الهاتف${escapeHtml(GO_PHONE)}
    ${(partyTwoName || partyTwoLines) ? `

    الطرف الثاني${partyTwoName ? `${escapeHtml(partyTwoName)}` : ''}

    ${partyTwoLines}
    ` : ''}
    ${contractTypeDisplayText ? `
    الاتفاق
    أن يقوم الطرف الأول بتنفيذ الخدمة / الخدمات التالية: ${escapeHtml(contractTypeDisplayText)} لمشروع الطرف الثاني الموضحة بياناته أدناه.
    ` : ''} ${projectCells ? `
    بيانات المشروع
    ${projectCells}
    ` : ''} ${featuresHtml ? `
    مزايا إضافية
    ${featuresHtml}
    ` : ''}
    بنود العقد
      ${clausesRows || '
    1. لا توجد بنود.
    2. '}
    ${featureClauseLines.length ? `
    بنود المزايا الإضافية
      ${featureClauseLines.map((fc) => `
    1. ${fc.name ? `${escapeHtml(fc.name)}: ` : ''}${escapeHtml(normalizeClauseLine(fc.clause))}
    2. `).join('')}
    ` : ''}
    قيمة العقد وتوزيع الدفعات
    قيمة العقد مبلغاً إجمالياً وقدره (${escapeHtml(toMoney(totalAmount))}) ريال. تُدفع حسب الجدول التالي:
    ${paymentRows || ''}
    #البيانالمبلغ
    لا توجد دفعات.
    الإجمالي${moneyHtml(totalAmount)}
    الإمضاء
    الطرف الأول
    ${escapeHtml(GO_COMPANY_AR)}
    الطرف الثاني
    ${escapeHtml(String(data.clientName || '-'))}