Commit 93215a48 authored by Fares's avatar Fares

fix(transfers): child separation gives NEW number, parent keeps theirs

Child separation is not a death/full transfer — the parent stays active
with their original membership number. The separated child gets a brand
new sequential number via MemberNumberGenerator::getNextMembershipNumber().

Only full_transfer type archives the source member and passes the same
number to the new member.
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 9ae45ad7
...@@ -79,8 +79,6 @@ final class TransferProcessor ...@@ -79,8 +79,6 @@ final class TransferProcessor
$recipientData = $notesDecoded['recipient_data'] ?? []; $recipientData = $notesDecoded['recipient_data'] ?? [];
} }
$sameNumber = $sourceMember['membership_number'];
// Find the separation_fee payment that was made for this transfer // Find the separation_fee payment that was made for this transfer
$transferPayment = $db->selectOne( $transferPayment = $db->selectOne(
"SELECT id FROM payments WHERE member_id = ? AND payment_type = 'separation_fee' AND related_entity_type = 'transfer_requests' AND related_entity_id = ? AND is_voided = 0 ORDER BY id DESC LIMIT 1", "SELECT id FROM payments WHERE member_id = ? AND payment_type = 'separation_fee' AND related_entity_type = 'transfer_requests' AND related_entity_id = ? AND is_voided = 0 ORDER BY id DESC LIMIT 1",
...@@ -88,7 +86,16 @@ final class TransferProcessor ...@@ -88,7 +86,16 @@ final class TransferProcessor
); );
$transferPaymentId = $transferPayment ? (int) $transferPayment['id'] : null; $transferPaymentId = $transferPayment ? (int) $transferPayment['id'] : null;
// 3. Archive source member FIRST — releases the unique membership_number // 3. Determine membership number for new member
$isChildSeparation = ($request['transfer_type'] === 'child_separation');
$newNumber = null;
if ($isChildSeparation) {
// Child separation: parent KEEPS their number, child gets a NEW number
$newNumber = (string) MemberNumberGenerator::getNextMembershipNumber();
} else {
// Full transfer / death: archive source, new member inherits SAME number
$newNumber = $sourceMember['membership_number'];
$db->update('members', [ $db->update('members', [
'membership_number' => null, 'membership_number' => null,
'status' => 'transferred', 'status' => 'transferred',
...@@ -97,16 +104,17 @@ final class TransferProcessor ...@@ -97,16 +104,17 @@ final class TransferProcessor
'updated_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s'),
], '`id` = ?', [(int) $sourceMember['id']]); ], '`id` = ?', [(int) $sourceMember['id']]);
// Verify number is actually released (no other member holds it) // Verify number is actually released
$numberConflict = $db->selectOne( $numberConflict = $db->selectOne(
"SELECT id FROM members WHERE membership_number = ? AND id != ?", "SELECT id FROM members WHERE membership_number = ? AND id != ?",
[$sameNumber, (int) $sourceMember['id']] [$newNumber, (int) $sourceMember['id']]
); );
if ($numberConflict) { if ($numberConflict) {
$sameNumber = MemberNumberGenerator::generateNext(); $newNumber = MemberNumberGenerator::generateNext();
}
} }
// 4. Create new member record with SAME membership number // 4. Create new member record
// Derive date_of_birth and gender from national_id if not directly available // Derive date_of_birth and gender from national_id if not directly available
$dob = $recipientData['date_of_birth'] ?? $subjectData['date_of_birth'] ?? null; $dob = $recipientData['date_of_birth'] ?? $subjectData['date_of_birth'] ?? null;
$gender = $recipientData['gender'] ?? $subjectData['gender'] ?? null; $gender = $recipientData['gender'] ?? $subjectData['gender'] ?? null;
...@@ -153,7 +161,7 @@ final class TransferProcessor ...@@ -153,7 +161,7 @@ final class TransferProcessor
'qualification_id' => $recipientData['qualification_id'] ?? $subjectData['qualification_id'] ?? $sourceMember['qualification_id'], 'qualification_id' => $recipientData['qualification_id'] ?? $subjectData['qualification_id'] ?? $sourceMember['qualification_id'],
'phone_mobile' => $phoneMobile, 'phone_mobile' => $phoneMobile,
'membership_value' => $request['new_membership_value'], 'membership_value' => $request['new_membership_value'],
'membership_number' => $sameNumber, 'membership_number' => $newNumber,
'created_at' => date('Y-m-d H:i:s'), 'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s'),
'created_by' => $employee ? (int) $employee->id : null, 'created_by' => $employee ? (int) $employee->id : null,
...@@ -171,7 +179,7 @@ final class TransferProcessor ...@@ -171,7 +179,7 @@ final class TransferProcessor
// 5. Record number chain // 5. Record number chain
ArchiveService::recordNumberTransfer( ArchiveService::recordNumberTransfer(
$sameNumber, $newNumber,
$request['transfer_type'], $request['transfer_type'],
'members', 'members',
$newMemberId, $newMemberId,
...@@ -335,7 +343,7 @@ final class TransferProcessor ...@@ -335,7 +343,7 @@ final class TransferProcessor
// 7. Mark transfer request complete // 7. Mark transfer request complete
$db->update('transfer_requests', [ $db->update('transfer_requests', [
'target_member_id' => $newMemberId, 'target_member_id' => $newMemberId,
'new_membership_number'=> $sameNumber, 'new_membership_number'=> $newNumber,
'archive_snapshot_id' => $snapshotId, 'archive_snapshot_id' => $snapshotId,
'completed_at' => date('Y-m-d H:i:s'), 'completed_at' => date('Y-m-d H:i:s'),
'status' => 'completed', 'status' => 'completed',
...@@ -349,7 +357,7 @@ final class TransferProcessor ...@@ -349,7 +357,7 @@ final class TransferProcessor
'transfer_id' => $transferRequestId, 'transfer_id' => $transferRequestId,
'source_member_id' => (int) $request['source_member_id'], 'source_member_id' => (int) $request['source_member_id'],
'target_member_id' => $newMemberId, 'target_member_id' => $newMemberId,
'new_number' => $sameNumber, 'new_number' => $newNumber,
'transfer_type' => $request['transfer_type'], 'transfer_type' => $request['transfer_type'],
'fee_amount' => $request['total_fee'] ?? $request['separation_fee'] ?? '0.00', 'fee_amount' => $request['total_fee'] ?? $request['separation_fee'] ?? '0.00',
'old_number' => $sourceMember['membership_number'] ?? null, 'old_number' => $sourceMember['membership_number'] ?? null,
...@@ -358,13 +366,13 @@ final class TransferProcessor ...@@ -358,13 +366,13 @@ final class TransferProcessor
Logger::info("Transfer completed", [ Logger::info("Transfer completed", [
'transfer_id' => $transferRequestId, 'transfer_id' => $transferRequestId,
'new_member' => $newMemberId, 'new_member' => $newMemberId,
'new_number' => $sameNumber, 'new_number' => $newNumber,
]); ]);
return [ return [
'success' => true, 'success' => true,
'new_member_id' => $newMemberId, 'new_member_id' => $newMemberId,
'new_number' => $sameNumber, 'new_number' => $newNumber,
'snapshot_id' => $snapshotId, 'snapshot_id' => $snapshotId,
'transfer_id' => $transferRequestId, 'transfer_id' => $transferRequestId,
]; ];
......
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