Commit 9612d215 authored by Mahmoud Aglan's avatar Mahmoud Aglan

Apply 2023/2024 discount in retroactive entry: server-side enforcement + UI hints

- RetroactiveMembershipService::createRetroactiveSubscription() now applies
  year-specific discounts from RuleEngine (SUBSCRIPTION_YEAR_ADJUSTMENT_{year})
  regardless of form input — server-side enforcement
- Retroactive wizard JS: uses year-specific rates (410/185 for 2023, 492/222
  for 2025+) and shows discount percentage visually per row
- total_amount = base - discount (dev fee excluded, added at invoice level)
- For paid rows: paid_amount includes dev fee since that's what was collected
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent e381d4e9
......@@ -642,10 +642,36 @@ final class RetroactiveMembershipService
$empId = $employee ? (int) $employee->id : null;
$status = $sub['status'] ?? 'pending';
$paidAmount = (string) ($sub['paid_amount'] ?? '0.00');
$paymentId = null;
$financialYear = $sub['financial_year'] ?? date('Y');
$yearStart = explode('/', $financialYear)[0];
if (!preg_match('/^\d{4}$/', $yearStart)) $yearStart = date('Y');
$personType = $sub['person_type'] ?? 'member';
$baseAmount = (string) ($sub['base_amount'] ?? '0.00');
$devFee = ($personType === 'member') ? '35.00' : '0.00';
// Apply year-specific discount from rules engine
$discountAmount = '0.00';
$yearAdjustment = \App\Modules\Rules\Services\RuleEngine::get('SUBSCRIPTION_YEAR_ADJUSTMENT_' . $yearStart);
if ($yearAdjustment && isset($yearAdjustment['discount_percentage'])) {
$discountAmount = bcdiv(bcmul($baseAmount, $yearAdjustment['discount_percentage'], 4), '100', 2);
}
// total_amount = base - discount (dev fee is separate, added at invoice level)
$totalAmount = bcsub($baseAmount, $discountAmount, 2);
// For paid status: paid_amount = total + dev fee (if member) since dev fee is part of what they paid
$paidAmount = '0.00';
if ($status === 'paid') {
$paidAmount = ($personType === 'member')
? bcadd($totalAmount, $devFee, 2)
: $totalAmount;
}
if (isset($sub['paid_amount']) && bccomp((string) $sub['paid_amount'], '0', 2) > 0) {
$paidAmount = (string) $sub['paid_amount'];
}
$paymentId = null;
if ($status === 'paid' && bccomp($paidAmount, '0', 2) > 0) {
$paymentId = self::createRetroactivePayment($memberId, [
'payment_type' => 'annual_subscription',
......@@ -656,19 +682,16 @@ final class RetroactiveMembershipService
]);
}
$yearStart = explode('/', $financialYear)[0];
if (!preg_match('/^\d{4}$/', $yearStart)) $yearStart = date('Y');
$subId = $db->insert('subscriptions', [
'member_id' => $memberId,
'financial_year' => $financialYear,
'person_type' => $sub['person_type'] ?? 'member',
'person_type' => $personType,
'person_id' => $sub['person_id'] ?? $memberId,
'person_name' => $sub['person_name'] ?? '',
'base_amount' => (string) ($sub['base_amount'] ?? '0.00'),
'development_fee' => (string) ($sub['development_fee'] ?? (($sub['person_type'] ?? 'member') === 'member' ? '35.00' : '0.00')),
'discount_amount' => (string) ($sub['discount_amount'] ?? '0.00'),
'total_amount' => (string) ($sub['total_amount'] ?? '0.00'),
'base_amount' => $baseAmount,
'development_fee' => $devFee,
'discount_amount' => $discountAmount,
'total_amount' => $totalAmount,
'paid_amount' => $paidAmount,
'fine_amount' => (string) ($sub['fine_amount'] ?? '0.00'),
'status' => $status,
......
......@@ -939,22 +939,44 @@ function addTempMember() {
</div>`;
}
// Subscriptions
// Subscriptions — year-specific rates and discounts
const YEAR_RATES = {
'2023': {member: 410, spouse: 410, child: 185, temp: 185, discount: 50},
'2024': {member: 410, spouse: 410, child: 185, temp: 185, discount: 0},
'2025': {member: 492, spouse: 492, child: 222, temp: 222, discount: 0},
};
const DEFAULT_RATES = {member: 492, spouse: 492, child: 222, temp: 222, discount: 0};
function getYearRates(fy) {
const yearStart = fy ? fy.split('/')[0] : '';
return YEAR_RATES[yearStart] || DEFAULT_RATES;
}
let subIdx = 0;
function addSubscriptionRow(year, status, baseAmt, devFee, personType, personIndex, personName) {
subIdx++;
document.getElementById('subscriptionCount').value = subIdx;
year = year || '';
status = status || 'pending';
baseAmt = baseAmt || '492.00';
devFee = devFee || '35.00';
personType = personType || 'member';
personIndex = personIndex || 0;
personName = personName || 'العضو';
const total = (parseFloat(baseAmt) + parseFloat(devFee)).toFixed(2);
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';
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;" id="subRow${subIdx}">
<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}">
......@@ -963,8 +985,8 @@ function addSubscriptionRow(year, status, baseAmt, devFee, personType, personInd
</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;"><input type="number" name="sub_dev_fee_${subIdx}" value="${devFee}" step="0.01" style="width:60px;padding:4px 6px;border:1px solid #D1D5DB;border-radius:4px;font-size:12px;"></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;"></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;"><input type="number" name="sub_paid_${subIdx}" value="${status === 'paid' ? total : '0'}" step="0.01" style="width:70px;padding:4px 6px;border:1px solid #D1D5DB;border-radius:4px;font-size:12px;"></td>
<td style="padding:6px;"><input type="number" name="sub_fine_${subIdx}" value="0" step="0.01" style="width:60px;padding:4px 6px;border:1px solid #D1D5DB;border-radius:4px;font-size:12px;"></td>
<td style="padding:6px;text-align:center;">
......@@ -991,12 +1013,12 @@ function generateSubscriptionYears() {
const currentYear = new Date().getFullYear();
const memberName = document.querySelector('[name=full_name_ar]').value || 'العضو';
// Member subscriptions
// 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, '492.00', '35.00', 'member', 0, memberName);
addSubscriptionRow(fy, status, null, '35.00', 'member', 0, memberName);
}
// Spouse subscriptions
......@@ -1011,7 +1033,7 @@ function generateSubscriptionYears() {
const fy = y + '/' + (y + 1);
const isPast = y < currentYear - 1;
const status = isPast ? 'paid' : (y === currentYear - 1 ? 'overdue' : 'pending');
addSubscriptionRow(fy, status, '492.00', '0.00', 'spouse', s, spouseName);
addSubscriptionRow(fy, status, null, '0.00', 'spouse', s, spouseName);
}
}
......@@ -1027,7 +1049,7 @@ function generateSubscriptionYears() {
const fy = y + '/' + (y + 1);
const isPast = y < currentYear - 1;
const status = isPast ? 'paid' : (y === currentYear - 1 ? 'overdue' : 'pending');
addSubscriptionRow(fy, status, '222.00', '0.00', 'child', c, childName);
addSubscriptionRow(fy, status, null, '0.00', 'child', c, childName);
}
}
......@@ -1043,7 +1065,7 @@ function generateSubscriptionYears() {
const fy = y + '/' + (y + 1);
const isPast = y < currentYear - 1;
const status = isPast ? 'paid' : (y === currentYear - 1 ? 'overdue' : 'pending');
addSubscriptionRow(fy, status, '222.00', '0.00', 'temporary', t, tempName);
addSubscriptionRow(fy, status, null, '0.00', 'temporary', t, tempName);
}
}
}
......
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