Commit 0ab98b7e authored by Fares's avatar Fares

refactor(retroactive-wizard): group subscriptions by year instead of per-person rows

Step 6 now shows year cards where each financial year contains all family
members (member + spouses + children + temps) with a single shared status
dropdown and payment date. Replaces the flat per-person-per-year table.
Backend format unchanged — same hidden input naming for full compatibility.
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent dd952bfe
......@@ -422,33 +422,15 @@ if ($wizardAlerts): ?>
<div class="card" style="margin-bottom:16px;">
<div style="padding:16px 20px;border-bottom:1px solid #E5E7EB;">
<h3 style="margin:0;font-size:16px;font-weight:700;"><i data-lucide="repeat" style="width:18px;height:18px;vertical-align:middle;margin-left:6px;"></i> الاشتراكات السنوية</h3>
<p style="margin:6px 0 0;font-size:12px;color:#6B7280;">أضف اشتراكات سنوية — مدفوعة أو متأخرة — لاختبار الغرامات والإسقاط</p>
<p style="margin:6px 0 0;font-size:12px;color:#6B7280;">أضف اشتراكات سنوية — مدفوعة أو متأخرة — فاختبار الغرامات والإسقاط</p>
</div>
<div style="padding:20px;">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
<button type="button" onclick="generateSubscriptionYears()" style="padding:6px 14px;background:#059669;color:#fff;border:none;border-radius:6px;font-size:12px;cursor:pointer;font-weight:600;"><i data-lucide="wand-2" style="width:13px;height:13px;vertical-align:middle;margin-left:4px;"></i> توليد تلقائي من تاريخ الانضمام</button>
<button type="button" onclick="addSubscriptionRow()" style="padding:6px 14px;background:#2563EB;color:#fff;border:none;border-radius:6px;font-size:12px;cursor:pointer;font-weight:600;">+ إضافة سنة يدوياً</button>
<button type="button" onclick="addYearCard()" style="padding:6px 14px;background:#2563EB;color:#fff;border:none;border-radius:6px;font-size:12px;cursor:pointer;font-weight:600;">+ إضافة سنة يدوياً</button>
</div>
<div style="max-height:400px;overflow-y:auto;">
<table style="width:100%;border-collapse:collapse;font-size:12px;" id="subsTable">
<thead>
<tr style="background:#F3F4F6;">
<th style="padding:8px;text-align:right;">الشخص</th>
<th style="padding:8px;text-align:right;">السنة المالية</th>
<th style="padding:8px;text-align:right;">المبلغ الأساسي</th>
<th style="padding:8px;text-align:right;">رسم تنمية</th>
<th style="padding:8px;text-align:right;">الإجمالي</th>
<th style="padding:8px;text-align:right;">المدفوع</th>
<th style="padding:8px;text-align:right;">الغرامة</th>
<th style="padding:8px;text-align:center;">الحالة</th>
<th style="padding:8px;text-align:right;">تاريخ السداد</th>
<th style="padding:8px;text-align:center;">حذف</th>
</tr>
</thead>
<tbody id="subscriptionRows"></tbody>
</table>
</div>
<div style="max-height:500px;overflow-y:auto;" id="subscriptionYearCards"></div>
<input type="hidden" name="subscription_count" id="subscriptionCount" value="0">
</div>
</div>
......@@ -971,119 +953,190 @@ function getYearRates(fy) {
}
let subIdx = 0;
function addSubscriptionRow(year, status, baseAmt, devFee, personType, personIndex, personName) {
subIdx++;
document.getElementById('subscriptionCount').value = subIdx;
year = year || '';
status = status || 'pending';
personType = personType || 'member';
personIndex = personIndex || 0;
personName = personName || 'العضو';
const rates = getYearRates(year);
if (!baseAmt) {
const rateKey = personType === 'temporary' ? 'temp' : personType;
baseAmt = (rates[rateKey] || 492).toFixed(2);
}
devFee = (personType === 'member') ? '35.00' : '0.00';
function getPersonTypeLabel(type) {
return {member: 'العضو', spouse: 'زوجة', child: 'ابن/ابنة', temporary: 'مؤقت'}[type] || type;
}
function buildYearCard(year, status, members) {
const fy = year + '/' + (year + 1);
const rates = getYearRates(fy);
const discPct = rates.discount || 0;
const discount = (parseFloat(baseAmt) * discPct / 100).toFixed(2);
const total = (parseFloat(baseAmt) - parseFloat(discount)).toFixed(2);
const discLabel = discPct > 0 ? `<span style="color:#7C3AED;font-size:10px;font-weight:600;">-${discPct}%</span>` : '';
document.getElementById('subscriptionRows').innerHTML += `
<tr style="border-bottom:1px solid #F3F4F6;${discPct > 0 ? 'background:#FAFAF9;' : ''}" id="subRow${subIdx}">
<td style="padding:6px;">
<span style="font-size:11px;color:#374151;white-space:nowrap;">${personName}</span>
<input type="hidden" name="sub_person_type_${subIdx}" value="${personType}">
<input type="hidden" name="sub_person_index_${subIdx}" value="${personIndex}">
<input type="hidden" name="sub_person_name_${subIdx}" value="${personName}">
</td>
<td style="padding:6px;"><input type="text" name="sub_year_${subIdx}" value="${year}" placeholder="2023/2024" style="width:90px;padding:4px 6px;border:1px solid #D1D5DB;border-radius:4px;font-size:12px;direction:ltr;"></td>
<td style="padding:6px;"><input type="number" name="sub_base_${subIdx}" value="${baseAmt}" step="0.01" style="width:70px;padding:4px 6px;border:1px solid #D1D5DB;border-radius:4px;font-size:12px;"></td>
<td style="padding:6px;">${discLabel}<input type="hidden" name="sub_discount_${subIdx}" value="${discount}"></td>
<td style="padding:6px;"><input type="number" name="sub_total_${subIdx}" value="${total}" step="0.01" style="width:70px;padding:4px 6px;border:1px solid #D1D5DB;border-radius:4px;font-size:12px;" readonly></td>
<td style="padding:6px;"><span style="font-size:12px;color:#6B7280;">${status === 'paid' ? total : '—'}</span><input type="hidden" name="sub_paid_${subIdx}" value="0"></td>
<td style="padding:6px;"><span style="font-size:12px;color:#6B7280;">تلقائي</span><input type="hidden" name="sub_fine_${subIdx}" value="0"></td>
<td style="padding:6px;text-align:center;">
<select name="sub_status_${subIdx}" style="padding:4px 6px;border:1px solid #D1D5DB;border-radius:4px;font-size:11px;" onchange="this.closest('tr').style.background=this.value==='paid'?'#F0FDF4':this.value==='overdue'?'#FEF2F2':'#fff'">
<option value="pending" ${status==='pending'?'selected':''}>معلق</option>
const statusColors = {paid: '#F0FDF4', overdue: '#FEF2F2', pending: '#fff', exempted: '#F5F3FF'};
const statusBorders = {paid: '#A7F3D0', overdue: '#FECACA', pending: '#E5E7EB', exempted: '#DDD6FE'};
const cardId = 'yearCard_' + year;
let totalAmount = 0;
let memberRows = '';
members.forEach(m => {
subIdx++;
const rateKey = m.type === 'temporary' ? 'temp' : m.type;
const baseAmt = (rates[rateKey] || 492).toFixed(2);
const devFee = m.type === 'member' ? '35.00' : '0.00';
const discount = (parseFloat(baseAmt) * discPct / 100).toFixed(2);
const total = (parseFloat(baseAmt) - parseFloat(discount)).toFixed(2);
totalAmount += parseFloat(total) + parseFloat(devFee);
const discLabel = discPct > 0 ? `<span style="color:#7C3AED;font-size:10px;font-weight:700;">-${discPct}%</span>` : '';
memberRows += `
<tr style="border-bottom:1px solid #F3F4F6;">
<td style="padding:6px 10px;font-size:12px;font-weight:600;color:#374151;">${m.name}</td>
<td style="padding:6px 10px;font-size:12px;color:#6B7280;">${getPersonTypeLabel(m.type)}</td>
<td style="padding:6px 10px;font-size:12px;direction:ltr;text-align:right;">${baseAmt}</td>
<td style="padding:6px 10px;font-size:12px;direction:ltr;text-align:right;">${discLabel} ${discount > 0 ? discount : '—'}</td>
<td style="padding:6px 10px;font-size:12px;direction:ltr;text-align:right;">${devFee !== '0.00' ? devFee : '—'}</td>
<td style="padding:6px 10px;font-size:13px;font-weight:700;direction:ltr;text-align:right;">${total}</td>
</tr>`;
memberRows += `
<input type="hidden" name="sub_person_type_${subIdx}" value="${m.type}">
<input type="hidden" name="sub_person_index_${subIdx}" value="${m.index}">
<input type="hidden" name="sub_person_name_${subIdx}" value="${m.name}">
<input type="hidden" name="sub_year_${subIdx}" value="${fy}">
<input type="hidden" name="sub_base_${subIdx}" value="${baseAmt}">
<input type="hidden" name="sub_discount_${subIdx}" value="${discount}">
<input type="hidden" name="sub_total_${subIdx}" value="${total}">
<input type="hidden" name="sub_paid_${subIdx}" value="0">
<input type="hidden" name="sub_fine_${subIdx}" value="0">
<input type="hidden" name="sub_status_${subIdx}" value="${status}" class="yearStatus_${year}">
<input type="hidden" name="sub_paid_date_${subIdx}" value="" class="yearPaidDate_${year}">
<input type="hidden" name="sub_dev_fee_${subIdx}" value="${devFee}">`;
});
document.getElementById('subscriptionCount').value = subIdx;
const html = `
<div id="${cardId}" style="margin-bottom:12px;border:1px solid ${statusBorders[status]};border-radius:10px;background:${statusColors[status]};overflow:hidden;">
<div style="padding:12px 16px;display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid #E5E7EB;background:rgba(0,0,0,0.02);">
<div style="display:flex;align-items:center;gap:12px;">
<span style="font-size:15px;font-weight:800;color:#1F2937;">${fy}</span>
<span style="font-size:12px;color:#6B7280;">${members.length} ${members.length === 1 ? 'شخص' : 'أشخاص'}</span>
<span style="font-size:13px;font-weight:700;color:#059669;">إجمالي: ${totalAmount.toFixed(2)} ج.م</span>
${discPct > 0 ? `<span style="background:#7C3AED;color:#fff;font-size:10px;padding:2px 8px;border-radius:10px;font-weight:700;">خصم ${discPct}%</span>` : ''}
</div>
<div style="display:flex;align-items:center;gap:10px;">
<select onchange="updateYearStatus('${year}', this.value, this.closest('[id^=yearCard]'))" style="padding:5px 10px;border:1px solid #D1D5DB;border-radius:6px;font-size:12px;font-weight:600;">
<option value="paid" ${status==='paid'?'selected':''}>مدفوع</option>
<option value="pending" ${status==='pending'?'selected':''}>معلق</option>
<option value="overdue" ${status==='overdue'?'selected':''}>متأخر</option>
<option value="exempted" ${status==='exempted'?'selected':''}>معفى</option>
</select>
</td>
<td style="padding:6px;"><input type="date" name="sub_paid_date_${subIdx}" style="padding:4px 6px;border:1px solid #D1D5DB;border-radius:4px;font-size:11px;width:110px;"></td>
<td style="padding:6px;text-align:center;"><button type="button" onclick="document.getElementById('subRow${subIdx}').remove()" style="color:#DC2626;cursor:pointer;border:none;background:none;font-size:16px;">×</button></td>
</tr>`;
<input type="date" onchange="updateYearPaidDate('${year}', this.value)" style="padding:5px 8px;border:1px solid #D1D5DB;border-radius:6px;font-size:11px;width:120px;">
<button type="button" onclick="document.getElementById('${cardId}').remove()" style="color:#DC2626;cursor:pointer;border:none;background:none;font-size:18px;font-weight:700;" title="حذف السنة">×</button>
</div>
</div>
<div style="padding:8px 16px;">
<table style="width:100%;border-collapse:collapse;font-size:12px;">
<thead>
<tr style="background:rgba(0,0,0,0.03);">
<th style="padding:6px 10px;text-align:right;font-weight:600;color:#6B7280;">الاسم</th>
<th style="padding:6px 10px;text-align:right;font-weight:600;color:#6B7280;">النوع</th>
<th style="padding:6px 10px;text-align:right;font-weight:600;color:#6B7280;">المبلغ</th>
<th style="padding:6px 10px;text-align:right;font-weight:600;color:#6B7280;">الخصم</th>
<th style="padding:6px 10px;text-align:right;font-weight:600;color:#6B7280;">تنمية</th>
<th style="padding:6px 10px;text-align:right;font-weight:600;color:#6B7280;">الصافي</th>
</tr>
</thead>
<tbody>${memberRows}</tbody>
</table>
</div>
</div>`;
return html;
}
function generateSubscriptionYears() {
const joinDate = document.querySelector('[name=join_date]').value;
if (!joinDate) { alert('حدد تاريخ الانضمام أولاً'); return; }
function updateYearStatus(year, newStatus, cardEl) {
const statusColors = {paid: '#F0FDF4', overdue: '#FEF2F2', pending: '#fff', exempted: '#F5F3FF'};
const statusBorders = {paid: '#A7F3D0', overdue: '#FECACA', pending: '#E5E7EB', exempted: '#DDD6FE'};
cardEl.style.background = statusColors[newStatus];
cardEl.style.borderColor = statusBorders[newStatus];
document.querySelectorAll('.yearStatus_' + year).forEach(el => el.value = newStatus);
}
document.getElementById('subscriptionRows').innerHTML = '';
subIdx = 0;
function updateYearPaidDate(year, dateVal) {
document.querySelectorAll('.yearPaidDate_' + year).forEach(el => el.value = dateVal);
}
const joinYear = parseInt(joinDate.split('-')[0]);
function addYearCard(year, status) {
const currentYear = new Date().getFullYear();
year = year || currentYear;
status = status || 'pending';
const members = collectFamilyMembers(year);
if (members.length === 0) {
alert('لا يوجد أعضاء لإضافة اشتراك لهم — تأكد من إدخال بيانات العضو والملحقين أولاً');
return;
}
const html = buildYearCard(year, status, members);
document.getElementById('subscriptionYearCards').innerHTML += html;
}
function collectFamilyMembers(year) {
const members = [];
const joinDate = document.querySelector('[name=join_date]').value;
const joinYear = joinDate ? parseInt(joinDate.split('-')[0]) : 0;
const memberName = document.querySelector('[name=full_name_ar]').value || 'العضو';
// Member subscriptions (rates auto-detected per year)
for (let y = joinYear; y <= currentYear; y++) {
const fy = y + '/' + (y + 1);
const isPast = y < currentYear - 1;
const status = isPast ? 'paid' : (y === currentYear - 1 ? 'overdue' : 'pending');
addSubscriptionRow(fy, status, null, '35.00', 'member', 0, memberName);
if (year >= joinYear) {
members.push({type: 'member', index: 0, name: memberName});
}
// Spouse subscriptions
const spouseCount = parseInt(document.getElementById('spouseCount').value) || 0;
for (let s = 1; s <= spouseCount; s++) {
const spouseNameEl = document.querySelector(`[name=spouse_name_${s}]`);
if (!spouseNameEl || !spouseNameEl.value) continue;
const spouseName = spouseNameEl.value || ('زوجة #' + s);
const spouseJoinEl = document.querySelector(`[name=spouse_join_date_${s}]`);
const spouseJoinYear = spouseJoinEl && spouseJoinEl.value ? parseInt(spouseJoinEl.value.split('-')[0]) : joinYear;
for (let y = spouseJoinYear; y <= currentYear; y++) {
const fy = y + '/' + (y + 1);
const isPast = y < currentYear - 1;
const status = isPast ? 'paid' : (y === currentYear - 1 ? 'overdue' : 'pending');
addSubscriptionRow(fy, status, null, '0.00', 'spouse', s, spouseName);
const nameEl = document.querySelector(`[name=spouse_name_${s}]`);
if (!nameEl || !nameEl.value) continue;
const jdEl = document.querySelector(`[name=spouse_join_date_${s}]`);
const spJoinYear = jdEl && jdEl.value ? parseInt(jdEl.value.split('-')[0]) : joinYear;
if (year >= spJoinYear) {
members.push({type: 'spouse', index: s, name: nameEl.value});
}
}
// Child subscriptions
const childCount = parseInt(document.getElementById('childCount').value) || 0;
for (let c = 1; c <= childCount; c++) {
const childNameEl = document.querySelector(`[name=child_name_${c}]`);
if (!childNameEl || !childNameEl.value) continue;
const childName = childNameEl.value || ('ابن #' + c);
const childJoinEl = document.querySelector(`[name=child_join_date_${c}]`);
const childJoinYear = childJoinEl && childJoinEl.value ? parseInt(childJoinEl.value.split('-')[0]) : joinYear;
for (let y = childJoinYear; y <= currentYear; y++) {
const fy = y + '/' + (y + 1);
const isPast = y < currentYear - 1;
const status = isPast ? 'paid' : (y === currentYear - 1 ? 'overdue' : 'pending');
addSubscriptionRow(fy, status, null, '0.00', 'child', c, childName);
const nameEl = document.querySelector(`[name=child_name_${c}]`);
if (!nameEl || !nameEl.value) continue;
const jdEl = document.querySelector(`[name=child_join_date_${c}]`);
const chJoinYear = jdEl && jdEl.value ? parseInt(jdEl.value.split('-')[0]) : joinYear;
if (year >= chJoinYear) {
members.push({type: 'child', index: c, name: nameEl.value});
}
}
// Temporary member subscriptions
const tempCount = parseInt(document.getElementById('tempMemberCount').value) || 0;
for (let t = 1; t <= tempCount; t++) {
const tempNameEl = document.querySelector(`[name=temp_name_${t}]`);
if (!tempNameEl || !tempNameEl.value) continue;
const tempName = tempNameEl.value || ('مؤقت #' + t);
const tempJoinEl = document.querySelector(`[name=temp_join_date_${t}]`);
const tempJoinYear = tempJoinEl && tempJoinEl.value ? parseInt(tempJoinEl.value.split('-')[0]) : joinYear;
for (let y = tempJoinYear; y <= currentYear; y++) {
const fy = y + '/' + (y + 1);
const isPast = y < currentYear - 1;
const status = isPast ? 'paid' : (y === currentYear - 1 ? 'overdue' : 'pending');
addSubscriptionRow(fy, status, null, '0.00', 'temporary', t, tempName);
const nameEl = document.querySelector(`[name=temp_name_${t}]`);
if (!nameEl || !nameEl.value) continue;
const jdEl = document.querySelector(`[name=temp_join_date_${t}]`);
const tmJoinYear = jdEl && jdEl.value ? parseInt(jdEl.value.split('-')[0]) : joinYear;
if (year >= tmJoinYear) {
members.push({type: 'temporary', index: t, name: nameEl.value});
}
}
return members;
}
function generateSubscriptionYears() {
const joinDate = document.querySelector('[name=join_date]').value;
if (!joinDate) { alert('حدد تاريخ الانضمام أولاً'); return; }
document.getElementById('subscriptionYearCards').innerHTML = '';
subIdx = 0;
const joinYear = parseInt(joinDate.split('-')[0]);
const currentYear = new Date().getFullYear();
for (let y = joinYear; y <= currentYear; y++) {
const isPast = y < currentYear - 1;
const status = isPast ? 'paid' : (y === currentYear - 1 ? 'overdue' : 'pending');
const members = collectFamilyMembers(y);
if (members.length > 0) {
const html = buildYearCard(y, status, members);
document.getElementById('subscriptionYearCards').innerHTML += html;
}
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment