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): ?> ...@@ -422,33 +422,15 @@ if ($wizardAlerts): ?>
<div class="card" style="margin-bottom:16px;"> <div class="card" style="margin-bottom:16px;">
<div style="padding:16px 20px;border-bottom:1px solid #E5E7EB;"> <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> <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>
<div style="padding:20px;"> <div style="padding:20px;">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;"> <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="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>
<div style="max-height:400px;overflow-y:auto;"> <div style="max-height:500px;overflow-y:auto;" id="subscriptionYearCards"></div>
<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>
<input type="hidden" name="subscription_count" id="subscriptionCount" value="0"> <input type="hidden" name="subscription_count" id="subscriptionCount" value="0">
</div> </div>
</div> </div>
...@@ -971,119 +953,190 @@ function getYearRates(fy) { ...@@ -971,119 +953,190 @@ function getYearRates(fy) {
} }
let subIdx = 0; 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 discPct = rates.discount || 0;
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 discount = (parseFloat(baseAmt) * discPct / 100).toFixed(2);
const total = (parseFloat(baseAmt) - parseFloat(discount)).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>`;
const discLabel = discPct > 0 ? `<span style="color:#7C3AED;font-size:10px;font-weight:600;">-${discPct}%</span>` : ''; 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('subscriptionRows').innerHTML += ` document.getElementById('subscriptionCount').value = subIdx;
<tr style="border-bottom:1px solid #F3F4F6;${discPct > 0 ? 'background:#FAFAF9;' : ''}" id="subRow${subIdx}">
<td style="padding:6px;"> const html = `
<span style="font-size:11px;color:#374151;white-space:nowrap;">${personName}</span> <div id="${cardId}" style="margin-bottom:12px;border:1px solid ${statusBorders[status]};border-radius:10px;background:${statusColors[status]};overflow:hidden;">
<input type="hidden" name="sub_person_type_${subIdx}" value="${personType}"> <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);">
<input type="hidden" name="sub_person_index_${subIdx}" value="${personIndex}"> <div style="display:flex;align-items:center;gap:12px;">
<input type="hidden" name="sub_person_name_${subIdx}" value="${personName}"> <span style="font-size:15px;font-weight:800;color:#1F2937;">${fy}</span>
</td> <span style="font-size:12px;color:#6B7280;">${members.length} ${members.length === 1 ? 'شخص' : 'أشخاص'}</span>
<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> <span style="font-size:13px;font-weight:700;color:#059669;">إجمالي: ${totalAmount.toFixed(2)} ج.م</span>
<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> ${discPct > 0 ? `<span style="background:#7C3AED;color:#fff;font-size:10px;padding:2px 8px;border-radius:10px;font-weight:700;">خصم ${discPct}%</span>` : ''}
<td style="padding:6px;">${discLabel}<input type="hidden" name="sub_discount_${subIdx}" value="${discount}"></td> </div>
<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> <div style="display:flex;align-items:center;gap:10px;">
<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> <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;">
<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>
<option value="paid" ${status==='paid'?'selected':''}>مدفوع</option> <option value="paid" ${status==='paid'?'selected':''}>مدفوع</option>
<option value="pending" ${status==='pending'?'selected':''}>معلق</option>
<option value="overdue" ${status==='overdue'?'selected':''}>متأخر</option> <option value="overdue" ${status==='overdue'?'selected':''}>متأخر</option>
<option value="exempted" ${status==='exempted'?'selected':''}>معفى</option> <option value="exempted" ${status==='exempted'?'selected':''}>معفى</option>
</select> </select>
</td> <input type="date" onchange="updateYearPaidDate('${year}', this.value)" style="padding:5px 8px;border:1px solid #D1D5DB;border-radius:6px;font-size:11px;width:120px;">
<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> <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>
<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> </div>
</tr>`; </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() { function updateYearStatus(year, newStatus, cardEl) {
const joinDate = document.querySelector('[name=join_date]').value; const statusColors = {paid: '#F0FDF4', overdue: '#FEF2F2', pending: '#fff', exempted: '#F5F3FF'};
if (!joinDate) { alert('حدد تاريخ الانضمام أولاً'); return; } 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 = ''; function updateYearPaidDate(year, dateVal) {
subIdx = 0; document.querySelectorAll('.yearPaidDate_' + year).forEach(el => el.value = dateVal);
}
const joinYear = parseInt(joinDate.split('-')[0]); function addYearCard(year, status) {
const currentYear = new Date().getFullYear(); 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 || 'العضو'; const memberName = document.querySelector('[name=full_name_ar]').value || 'العضو';
// Member subscriptions (rates auto-detected per year) if (year >= joinYear) {
for (let y = joinYear; y <= currentYear; y++) { members.push({type: 'member', index: 0, name: memberName});
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);
} }
// Spouse subscriptions
const spouseCount = parseInt(document.getElementById('spouseCount').value) || 0; const spouseCount = parseInt(document.getElementById('spouseCount').value) || 0;
for (let s = 1; s <= spouseCount; s++) { for (let s = 1; s <= spouseCount; s++) {
const spouseNameEl = document.querySelector(`[name=spouse_name_${s}]`); const nameEl = document.querySelector(`[name=spouse_name_${s}]`);
if (!spouseNameEl || !spouseNameEl.value) continue; if (!nameEl || !nameEl.value) continue;
const spouseName = spouseNameEl.value || ('زوجة #' + s); const jdEl = document.querySelector(`[name=spouse_join_date_${s}]`);
const spouseJoinEl = document.querySelector(`[name=spouse_join_date_${s}]`); const spJoinYear = jdEl && jdEl.value ? parseInt(jdEl.value.split('-')[0]) : joinYear;
const spouseJoinYear = spouseJoinEl && spouseJoinEl.value ? parseInt(spouseJoinEl.value.split('-')[0]) : joinYear; if (year >= spJoinYear) {
for (let y = spouseJoinYear; y <= currentYear; y++) { members.push({type: 'spouse', index: s, name: nameEl.value});
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);
} }
} }
// Child subscriptions
const childCount = parseInt(document.getElementById('childCount').value) || 0; const childCount = parseInt(document.getElementById('childCount').value) || 0;
for (let c = 1; c <= childCount; c++) { for (let c = 1; c <= childCount; c++) {
const childNameEl = document.querySelector(`[name=child_name_${c}]`); const nameEl = document.querySelector(`[name=child_name_${c}]`);
if (!childNameEl || !childNameEl.value) continue; if (!nameEl || !nameEl.value) continue;
const childName = childNameEl.value || ('ابن #' + c); const jdEl = document.querySelector(`[name=child_join_date_${c}]`);
const childJoinEl = document.querySelector(`[name=child_join_date_${c}]`); const chJoinYear = jdEl && jdEl.value ? parseInt(jdEl.value.split('-')[0]) : joinYear;
const childJoinYear = childJoinEl && childJoinEl.value ? parseInt(childJoinEl.value.split('-')[0]) : joinYear; if (year >= chJoinYear) {
for (let y = childJoinYear; y <= currentYear; y++) { members.push({type: 'child', index: c, name: nameEl.value});
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);
} }
} }
// Temporary member subscriptions
const tempCount = parseInt(document.getElementById('tempMemberCount').value) || 0; const tempCount = parseInt(document.getElementById('tempMemberCount').value) || 0;
for (let t = 1; t <= tempCount; t++) { for (let t = 1; t <= tempCount; t++) {
const tempNameEl = document.querySelector(`[name=temp_name_${t}]`); const nameEl = document.querySelector(`[name=temp_name_${t}]`);
if (!tempNameEl || !tempNameEl.value) continue; if (!nameEl || !nameEl.value) continue;
const tempName = tempNameEl.value || ('مؤقت #' + t); const jdEl = document.querySelector(`[name=temp_join_date_${t}]`);
const tempJoinEl = document.querySelector(`[name=temp_join_date_${t}]`); const tmJoinYear = jdEl && jdEl.value ? parseInt(jdEl.value.split('-')[0]) : joinYear;
const tempJoinYear = tempJoinEl && tempJoinEl.value ? parseInt(tempJoinEl.value.split('-')[0]) : joinYear; if (year >= tmJoinYear) {
for (let y = tempJoinYear; y <= currentYear; y++) { members.push({type: 'temporary', index: t, name: nameEl.value});
const fy = y + '/' + (y + 1); }
}
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 isPast = y < currentYear - 1;
const status = isPast ? 'paid' : (y === currentYear - 1 ? 'overdue' : 'pending'); const status = isPast ? 'paid' : (y === currentYear - 1 ? 'overdue' : 'pending');
addSubscriptionRow(fy, status, null, '0.00', 'temporary', t, tempName); 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