Snapshot of main at 9f39a6e1e (2026-11-02)
This commit is contained in:
commit
6ccf1792d7
2580 files changed
+1040202
No files matched your search
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Bank;
|
||||
use App\Models\Locations\Location;
|
||||
use App\Models\Organization;
|
||||
use App\Models\User;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class BackfillBankClientsByLocation extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'bank-clients:backfill-by-location {--dry-run : Preview the assignments without writing to the database}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Link Users/Organizations to a local (level 2) Bank via bank_clients, matched by shared city_id on their Location record';
|
||||
|
||||
/**
|
||||
* The console command help text, shown by `php artisan help bank-clients:backfill-by-location`.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $help = <<<'HELP'
|
||||
Populates the bank_clients pivot table, which bank-manager features (the
|
||||
scoped "Manage Profiles" view, and the transaction reversal feature) use to
|
||||
determine which Users/Organizations belong to their bank.
|
||||
|
||||
bank_clients is never populated by normal registration, so on any
|
||||
environment where these features are newly enabled, most or all profiles
|
||||
will have no bank client link yet, and a bank-manager's scoped views will
|
||||
appear empty even for profiles that clearly belong to that bank's city.
|
||||
|
||||
WHAT IT DOES
|
||||
For every level-2 Bank that has a Location with a city_id set, this finds
|
||||
every User/Organization whose own Location has a matching city_id, and
|
||||
links them via a "local" bank_clients row -- unless that profile already
|
||||
has one, in which case it is skipped.
|
||||
|
||||
Only Users and Organizations are ever linked as clients. Banks, Admins,
|
||||
and banks at level 0 (central) or level 1 (system/bridge) are never
|
||||
targets or clients.
|
||||
|
||||
SAFETY
|
||||
Idempotent -- safe to run repeatedly. Already-linked profiles are always
|
||||
skipped, never duplicated or reassigned. Never deletes or modifies an
|
||||
existing bank_clients row. Profiles whose city does not match any bank
|
||||
are simply left unlinked.
|
||||
|
||||
EXAMPLES
|
||||
Preview only, no database writes:
|
||||
php artisan bank-clients:backfill-by-location --dry-run
|
||||
|
||||
Apply for real:
|
||||
php artisan bank-clients:backfill-by-location
|
||||
|
||||
PRODUCTION USAGE
|
||||
Always run with --dry-run first and check that every bank you expect to
|
||||
receive clients appears in the printed "Bank #... -> city_id ..." list.
|
||||
A missing bank usually means its Location has no city_id set -- fix that
|
||||
on the bank's profile first, then re-run the dry-run before applying.
|
||||
|
||||
See references/BANK_CLIENTS_BACKFILL_COMMAND.md for full documentation.
|
||||
HELP;
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$dryRun = (bool) $this->option('dry-run');
|
||||
|
||||
$banks = Bank::where('level', 2)->get()->filter(function (Bank $bank) {
|
||||
return (bool) $bank->locations()->whereNotNull('city_id')->exists();
|
||||
});
|
||||
|
||||
if ($banks->isEmpty()) {
|
||||
$this->error('No level-2 banks with a city_id location found. Nothing to do.');
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$bankCityMap = [];
|
||||
foreach ($banks as $bank) {
|
||||
$cityId = $bank->locations()->whereNotNull('city_id')->value('city_id');
|
||||
$bankCityMap[$cityId] = $bank;
|
||||
$this->line("Bank #{$bank->id} \"{$bank->name}\" -> city_id {$cityId}");
|
||||
}
|
||||
|
||||
$cityIds = array_keys($bankCityMap);
|
||||
|
||||
$this->newLine();
|
||||
$this->info($dryRun ? 'DRY RUN — no changes will be written.' : 'Writing bank_clients rows.');
|
||||
$this->newLine();
|
||||
|
||||
$totalLinked = 0;
|
||||
$totalSkippedAlreadyLinked = 0;
|
||||
|
||||
foreach (['user' => User::class, 'organization' => Organization::class] as $label => $modelClass) {
|
||||
$matches = Location::where('locatable_type', $modelClass)
|
||||
->whereIn('city_id', $cityIds)
|
||||
->get(['locatable_id', 'city_id']);
|
||||
|
||||
$this->info(ucfirst($label) . "s matched by city: {$matches->count()}");
|
||||
|
||||
$bar = $this->output->createProgressBar($matches->count());
|
||||
$bar->start();
|
||||
|
||||
foreach ($matches as $location) {
|
||||
$bank = $bankCityMap[$location->city_id];
|
||||
$client = $modelClass::find($location->locatable_id);
|
||||
|
||||
if (!$client) {
|
||||
$bar->advance();
|
||||
continue;
|
||||
}
|
||||
|
||||
$alreadyLinked = $client->banksClient()->exists();
|
||||
if ($alreadyLinked) {
|
||||
$totalSkippedAlreadyLinked++;
|
||||
$bar->advance();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$dryRun) {
|
||||
$client->attachBankClient($bank, 'local');
|
||||
}
|
||||
$totalLinked++;
|
||||
$bar->advance();
|
||||
}
|
||||
|
||||
$bar->finish();
|
||||
$this->newLine(2);
|
||||
}
|
||||
|
||||
$this->info("Linked: {$totalLinked}");
|
||||
$this->info("Skipped (already linked): {$totalSkippedAlreadyLinked}");
|
||||
|
||||
if ($dryRun) {
|
||||
$this->comment('Run without --dry-run to apply these changes.');
|
||||
}
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user