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
$recipientData = $notesDecoded['recipient_data'] ?? [];
}
$sameNumber = $sourceMember['membership_number'];
// Find the separation_fee payment that was made for this transfer
$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",
......@@ -88,25 +86,35 @@ final class TransferProcessor
);
$transferPaymentId = $transferPayment ? (int) $transferPayment['id'] : null;
// 3. Archive source member FIRST — releases the unique membership_number
$db->update('members', [
'membership_number' => null,
'status' => 'transferred',
'is_archived' => 1,
'archived_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
], '`id` = ?', [(int) $sourceMember['id']]);
// Verify number is actually released (no other member holds it)
$numberConflict = $db->selectOne(
"SELECT id FROM members WHERE membership_number = ? AND id != ?",
[$sameNumber, (int) $sourceMember['id']]
);
if ($numberConflict) {
$sameNumber = MemberNumberGenerator::generateNext();
// 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', [
'membership_number' => null,
'status' => 'transferred',
'is_archived' => 1,
'archived_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
], '`id` = ?', [(int) $sourceMember['id']]);
// Verify number is actually released
$numberConflict = $db->selectOne(
"SELECT id FROM members WHERE membership_number = ? AND id != ?",
[$newNumber, (int) $sourceMember['id']]
);
if ($numberConflict) {
$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
$dob = $recipientData['date_of_birth'] ?? $subjectData['date_of_birth'] ?? null;
$gender = $recipientData['gender'] ?? $subjectData['gender'] ?? null;
......@@ -153,7 +161,7 @@ final class TransferProcessor
'qualification_id' => $recipientData['qualification_id'] ?? $subjectData['qualification_id'] ?? $sourceMember['qualification_id'],
'phone_mobile' => $phoneMobile,
'membership_value' => $request['new_membership_value'],
'membership_number' => $sameNumber,
'membership_number' => $newNumber,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
'created_by' => $employee ? (int) $employee->id : null,
......@@ -171,7 +179,7 @@ final class TransferProcessor
// 5. Record number chain
ArchiveService::recordNumberTransfer(
$sameNumber,
$newNumber,
$request['transfer_type'],
'members',
$newMemberId,
......@@ -335,7 +343,7 @@ final class TransferProcessor
// 7. Mark transfer request complete
$db->update('transfer_requests', [
'target_member_id' => $newMemberId,
'new_membership_number'=> $sameNumber,
'new_membership_number'=> $newNumber,
'archive_snapshot_id' => $snapshotId,
'completed_at' => date('Y-m-d H:i:s'),
'status' => 'completed',
......@@ -349,7 +357,7 @@ final class TransferProcessor
'transfer_id' => $transferRequestId,
'source_member_id' => (int) $request['source_member_id'],
'target_member_id' => $newMemberId,
'new_number' => $sameNumber,
'new_number' => $newNumber,
'transfer_type' => $request['transfer_type'],
'fee_amount' => $request['total_fee'] ?? $request['separation_fee'] ?? '0.00',
'old_number' => $sourceMember['membership_number'] ?? null,
......@@ -358,13 +366,13 @@ final class TransferProcessor
Logger::info("Transfer completed", [
'transfer_id' => $transferRequestId,
'new_member' => $newMemberId,
'new_number' => $sameNumber,
'new_number' => $newNumber,
]);
return [
'success' => true,
'new_member_id' => $newMemberId,
'new_number' => $sameNumber,
'new_number' => $newNumber,
'snapshot_id' => $snapshotId,
'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