Update roster import to sync existing members
This commit is contained in:
@@ -64,7 +64,8 @@ class ImportMembersCommand extends Command
|
|||||||
10 => 'email', // e-mail
|
10 => 'email', // e-mail
|
||||||
];
|
];
|
||||||
|
|
||||||
$imported = 0;
|
$created = 0;
|
||||||
|
$updated = 0;
|
||||||
$skipped = 0;
|
$skipped = 0;
|
||||||
$errors = [];
|
$errors = [];
|
||||||
|
|
||||||
@@ -91,59 +92,64 @@ class ImportMembersCommand extends Command
|
|||||||
$email = $this->resolveEmail($name, $row[10] ?? '');
|
$email = $this->resolveEmail($name, $row[10] ?? '');
|
||||||
$memberType = Member::TYPE_INDIVIDUAL;
|
$memberType = Member::TYPE_INDIVIDUAL;
|
||||||
|
|
||||||
// Validate phone for password generation
|
$existingMember = $this->findExistingMember($email, $memberNumber, $nationalId, $name);
|
||||||
if (strlen($phone) < 4) {
|
if ($existingMember) {
|
||||||
$errors[] = "Row {$rowNum}: {$name} - Invalid phone number for password";
|
$updateData = $this->buildUpdateData(
|
||||||
$skipped++;
|
$existingMember,
|
||||||
|
$memberNumber,
|
||||||
|
$birthDate,
|
||||||
|
$gender,
|
||||||
|
$occupation,
|
||||||
|
$address,
|
||||||
|
$phone,
|
||||||
|
$nationalId,
|
||||||
|
$identityType,
|
||||||
|
$identityOtherText,
|
||||||
|
$applyDate,
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($dryRun) {
|
||||||
|
$this->line("[DRY RUN] Would update: {$name} (member_id {$existingMember->id})");
|
||||||
|
$updated++;
|
||||||
|
} elseif (! empty($updateData)) {
|
||||||
|
$existingMember->update($updateData);
|
||||||
|
$this->info("Updated: {$name} (member_id {$existingMember->id})");
|
||||||
|
$updated++;
|
||||||
|
} else {
|
||||||
|
$skipped++;
|
||||||
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate password from last 4 digits
|
|
||||||
$password = substr($phone, -4);
|
|
||||||
|
|
||||||
// Check for existing user by email
|
|
||||||
$existingUser = User::where('email', $email)->first();
|
$existingUser = User::where('email', $email)->first();
|
||||||
if ($existingUser) {
|
$isNewUser = false;
|
||||||
$this->warn("Row {$rowNum}: {$name} - Email already exists: {$email}");
|
|
||||||
$skipped++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($memberNumber) {
|
|
||||||
$existingMemberNumber = Member::where('member_number', $memberNumber)->first();
|
|
||||||
if ($existingMemberNumber) {
|
|
||||||
$this->warn("Row {$rowNum}: {$name} - Member number already exists: {$memberNumber}");
|
|
||||||
$skipped++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for existing member by national ID
|
|
||||||
if ($nationalId) {
|
|
||||||
$existingMember = Member::where('national_id_hash', hash('sha256', $nationalId))->first();
|
|
||||||
if ($existingMember) {
|
|
||||||
$this->warn("Row {$rowNum}: {$name} - National ID already exists");
|
|
||||||
$skipped++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($dryRun) {
|
if ($dryRun) {
|
||||||
$this->line("[DRY RUN] Would import: {$name}, Phone: {$phone}, Email: {$email}, Password: ****{$password}");
|
$this->line("[DRY RUN] Would create: {$name}, Phone: {$phone}, Email: {$email}");
|
||||||
$imported++;
|
$created++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create User
|
if (! $existingUser) {
|
||||||
$user = User::create([
|
if (strlen($phone) < 4) {
|
||||||
'name' => $name,
|
$errors[] = "Row {$rowNum}: {$name} - Invalid phone number for password";
|
||||||
'email' => $email,
|
$skipped++;
|
||||||
'password' => Hash::make($password),
|
continue;
|
||||||
]);
|
}
|
||||||
|
|
||||||
|
$password = substr($phone, -4);
|
||||||
|
$existingUser = User::create([
|
||||||
|
'name' => $name,
|
||||||
|
'email' => $email,
|
||||||
|
'password' => Hash::make($password),
|
||||||
|
]);
|
||||||
|
$isNewUser = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Create Member
|
// Create Member
|
||||||
$member = Member::create([
|
$member = Member::create([
|
||||||
'user_id' => $user->id,
|
'user_id' => $existingUser->id,
|
||||||
'member_number' => $memberNumber ?: null,
|
'member_number' => $memberNumber ?: null,
|
||||||
'full_name' => $name,
|
'full_name' => $name,
|
||||||
'email' => $email,
|
'email' => $email,
|
||||||
@@ -166,6 +172,10 @@ class ImportMembersCommand extends Command
|
|||||||
'membership_expires_at' => now()->endOfYear(), // 2026-12-31
|
'membership_expires_at' => now()->endOfYear(), // 2026-12-31
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if ($isNewUser) {
|
||||||
|
$this->info("Created user: {$name} ({$email})");
|
||||||
|
}
|
||||||
|
|
||||||
// Create fully approved MembershipPayment
|
// Create fully approved MembershipPayment
|
||||||
MembershipPayment::create([
|
MembershipPayment::create([
|
||||||
'member_id' => $member->id,
|
'member_id' => $member->id,
|
||||||
@@ -184,8 +194,8 @@ class ImportMembersCommand extends Command
|
|||||||
'notes' => 'Imported from legacy roster - pre-approved',
|
'notes' => 'Imported from legacy roster - pre-approved',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->info("Imported: {$name} ({$email})");
|
$this->info("Created: {$name} ({$email})");
|
||||||
$imported++;
|
$created++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$dryRun) {
|
if (!$dryRun) {
|
||||||
@@ -200,7 +210,8 @@ class ImportMembersCommand extends Command
|
|||||||
|
|
||||||
$this->newLine();
|
$this->newLine();
|
||||||
$this->info("Import complete!");
|
$this->info("Import complete!");
|
||||||
$this->info(" Imported: {$imported}");
|
$this->info(" Created: {$created}");
|
||||||
|
$this->info(" Updated: {$updated}");
|
||||||
$this->info(" Skipped: {$skipped}");
|
$this->info(" Skipped: {$skipped}");
|
||||||
|
|
||||||
if (!empty($errors)) {
|
if (!empty($errors)) {
|
||||||
@@ -394,6 +405,113 @@ class ImportMembersCommand extends Command
|
|||||||
return [Member::IDENTITY_OTHER, $raw];
|
return [Member::IDENTITY_OTHER, $raw];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function findExistingMember(string $email, ?string $memberNumber, string $nationalId, string $name): ?Member
|
||||||
|
{
|
||||||
|
if ($email !== '' && filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||||
|
$user = User::where('email', $email)->first();
|
||||||
|
if ($user && $user->member) {
|
||||||
|
return $user->member;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($memberNumber) {
|
||||||
|
$member = Member::where('member_number', $memberNumber)->first();
|
||||||
|
if ($member) {
|
||||||
|
return $member;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($nationalId !== '') {
|
||||||
|
$member = Member::where('national_id_hash', hash('sha256', $nationalId))->first();
|
||||||
|
if ($member) {
|
||||||
|
return $member;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$matches = Member::where('full_name', $name)->get();
|
||||||
|
if ($matches->count() === 1) {
|
||||||
|
return $matches->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($matches->count() > 1) {
|
||||||
|
$nonPlaceholder = $matches->filter(function (Member $member) {
|
||||||
|
return ! $this->isPlaceholderEmail($member->email);
|
||||||
|
});
|
||||||
|
if ($nonPlaceholder->count() === 1) {
|
||||||
|
return $nonPlaceholder->first();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function isPlaceholderEmail(?string $email): bool
|
||||||
|
{
|
||||||
|
if (! $email) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return str_ends_with($email, '@member.usher.org.tw');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function buildUpdateData(
|
||||||
|
Member $member,
|
||||||
|
?string $memberNumber,
|
||||||
|
?string $birthDate,
|
||||||
|
?string $gender,
|
||||||
|
string $occupation,
|
||||||
|
string $address,
|
||||||
|
string $phone,
|
||||||
|
string $nationalId,
|
||||||
|
?string $identityType,
|
||||||
|
?string $identityOtherText,
|
||||||
|
?string $appliedAt,
|
||||||
|
): array {
|
||||||
|
$update = [];
|
||||||
|
|
||||||
|
if ($memberNumber && ! $member->member_number) {
|
||||||
|
$update['member_number'] = $memberNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($birthDate && ! $member->birth_date) {
|
||||||
|
$update['birth_date'] = $birthDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($gender && ! $member->gender) {
|
||||||
|
$update['gender'] = $gender;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($occupation !== '' && ! $member->occupation) {
|
||||||
|
$update['occupation'] = $occupation;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($address !== '' && ! $member->address_line_1) {
|
||||||
|
$update['address_line_1'] = $address;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($phone !== '' && ! $member->phone) {
|
||||||
|
$update['phone'] = $phone;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($nationalId !== '' && empty($member->national_id_encrypted)) {
|
||||||
|
$update['national_id'] = $nationalId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($identityType && ! $member->identity_type) {
|
||||||
|
$update['identity_type'] = $identityType;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($identityOtherText && ! $member->identity_other_text) {
|
||||||
|
$update['identity_other_text'] = $identityOtherText;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($appliedAt && ! $member->applied_at) {
|
||||||
|
$update['applied_at'] = $appliedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $update;
|
||||||
|
}
|
||||||
|
|
||||||
protected function toPinyin(string $name): string
|
protected function toPinyin(string $name): string
|
||||||
{
|
{
|
||||||
// Simple romanization for email generation
|
// Simple romanization for email generation
|
||||||
|
|||||||
Reference in New Issue
Block a user