Commit 0959adae authored by Mahmoud Aglan's avatar Mahmoud Aglan

fix(subscriptions): remove subs for inactive dependents, fix Waiver boot crash

1. Added phase to SubscriptionDataMigration that removes unpaid
   subscriptions for dependents who are archived or no longer active
   (e.g., temporary members who became inactive, frozen children).

2. Fixed WaiverProcessor referencing non-existent 'reason' column in
   fines table — correct column is 'notes'.
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 5ff54e5b
...@@ -32,6 +32,7 @@ final class SubscriptionDataMigration ...@@ -32,6 +32,7 @@ final class SubscriptionDataMigration
private array $stats = [ private array $stats = [
'members_processed' => 0, 'members_processed' => 0,
'pre_2023_deleted' => 0, 'pre_2023_deleted' => 0,
'inactive_removed' => 0,
'subscriptions_created' => 0, 'subscriptions_created' => 0,
'orphaned_ids_fixed' => 0, 'orphaned_ids_fixed' => 0,
'duplicates_removed' => 0, 'duplicates_removed' => 0,
...@@ -54,6 +55,9 @@ final class SubscriptionDataMigration ...@@ -54,6 +55,9 @@ final class SubscriptionDataMigration
// Phase 0: Remove subscriptions before the system start (2023/2024) // Phase 0: Remove subscriptions before the system start (2023/2024)
$this->removePreSystemSubscriptions($dryRun); $this->removePreSystemSubscriptions($dryRun);
// Phase 0.5: Remove subscriptions for inactive/archived dependents
$this->removeInactiveDependentSubscriptions($dryRun);
// Phase 1: Fix orphaned person_id references // Phase 1: Fix orphaned person_id references
$this->fixOrphanedPersonIds($dryRun); $this->fixOrphanedPersonIds($dryRun);
...@@ -103,6 +107,33 @@ final class SubscriptionDataMigration ...@@ -103,6 +107,33 @@ final class SubscriptionDataMigration
} }
} }
private function removeInactiveDependentSubscriptions(bool $dryRun): void
{
$types = [
'spouse' => 'spouses',
'child' => 'children',
'temporary' => 'temporary_members',
];
foreach ($types as $personType => $table) {
// Find unpaid subscriptions where the dependent is no longer active
$stale = $this->db->select(
"SELECT s.id FROM subscriptions s
JOIN {$table} t ON t.id = s.person_id AND t.member_id = s.member_id
WHERE s.person_type = ? AND s.status != 'paid'
AND (t.is_archived = 1 OR t.status != 'active')",
[$personType]
);
foreach ($stale as $row) {
if (!$dryRun) {
$this->db->query("DELETE FROM subscriptions WHERE id = ?", [(int)$row['id']]);
}
$this->stats['inactive_removed']++;
}
}
}
private function fixOrphanedPersonIds(bool $dryRun): void private function fixOrphanedPersonIds(bool $dryRun): void
{ {
$types = [ $types = [
......
...@@ -196,7 +196,7 @@ final class WaiverProcessor ...@@ -196,7 +196,7 @@ final class WaiverProcessor
// 2. Fines (member-level — system stores all fines under member_id) // 2. Fines (member-level — system stores all fines under member_id)
$fines = $db->select( $fines = $db->select(
"SELECT id, reason, (amount - paid_amount) as due, created_at FROM fines WHERE member_id = ? AND status IN ('imposed','pending','appeal_upheld') AND (amount - paid_amount) > 0", "SELECT id, notes, (amount - paid_amount) as due, created_at FROM fines WHERE member_id = ? AND status IN ('imposed','pending','appeal_upheld') AND (amount - paid_amount) > 0",
[$memberId] [$memberId]
); );
foreach ($fines as $fine) { foreach ($fines as $fine) {
...@@ -210,7 +210,7 @@ final class WaiverProcessor ...@@ -210,7 +210,7 @@ final class WaiverProcessor
'debt_type_code' => 'fine', 'debt_type_code' => 'fine',
'period' => substr($fine['created_at'] ?? '', 0, 10), 'period' => substr($fine['created_at'] ?? '', 0, 10),
'amount' => $fine['due'], 'amount' => $fine['due'],
'description' => 'غرامة' . (!empty($fine['reason']) ? ' — ' . $fine['reason'] : '') . ' — العضو الأساسي', 'description' => 'غرامة' . (!empty($fine['notes']) ? ' — ' . $fine['notes'] : '') . ' — العضو الأساسي',
'payment_url' => '/payments/process/' . $memberId, 'payment_url' => '/payments/process/' . $memberId,
]; ];
} }
......
...@@ -159,10 +159,13 @@ switch ($command) { ...@@ -159,10 +159,13 @@ switch ($command) {
$result = $migration->run($dryRun); $result = $migration->run($dryRun);
echo " Members processed: {$result['members_processed']}\n"; echo " Members processed: {$result['members_processed']}\n";
echo " Pre-2023 deleted: {$result['pre_2023_deleted']}\n";
echo " Inactive deps removed: {$result['inactive_removed']}\n";
echo " Subscriptions created: {$result['subscriptions_created']}\n"; echo " Subscriptions created: {$result['subscriptions_created']}\n";
echo " Orphaned IDs fixed: {$result['orphaned_ids_fixed']}\n"; echo " Orphaned IDs fixed: {$result['orphaned_ids_fixed']}\n";
echo " Duplicates removed: {$result['duplicates_removed']}\n"; echo " Duplicates removed: {$result['duplicates_removed']}\n";
echo " Amount corrections: {$result['amount_corrections']}\n"; echo " Amount corrections: {$result['amount_corrections']}\n";
echo " Discount corrections: {$result['discount_corrections']}\n";
echo " Fines applied: {$result['fines_applied']}\n"; echo " Fines applied: {$result['fines_applied']}\n";
if (!empty($result['errors'])) { if (!empty($result['errors'])) {
echo "\n ⚠️ Errors:\n"; echo "\n ⚠️ Errors:\n";
......
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