Emergency button for missing persons
This commit is contained in:
parent
c177264b0b
commit
86898eac1a
|
|
@ -228,11 +228,12 @@ public function updateContract(ClientCase $clientCase, string $uuid, UpdateContr
|
||||||
public function debugContractAccounts(ClientCase $clientCase, string $uuid, Request $request)
|
public function debugContractAccounts(ClientCase $clientCase, string $uuid, Request $request)
|
||||||
{
|
{
|
||||||
abort_unless(config('app.debug'), 404);
|
abort_unless(config('app.debug'), 404);
|
||||||
$contract = $clientCase->contracts()->where('uuid', $uuid)->firstOrFail(['id','uuid','reference']);
|
$contract = $clientCase->contracts()->where('uuid', $uuid)->firstOrFail(['id', 'uuid', 'reference']);
|
||||||
$accounts = \DB::table('accounts')
|
$accounts = \DB::table('accounts')
|
||||||
->where('contract_id', $contract->id)
|
->where('contract_id', $contract->id)
|
||||||
->orderBy('id')
|
->orderBy('id')
|
||||||
->get(['id','contract_id','initial_amount','balance_amount','type_id','created_at','updated_at']);
|
->get(['id', 'contract_id', 'initial_amount', 'balance_amount', 'type_id', 'created_at', 'updated_at']);
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'contract' => $contract,
|
'contract' => $contract,
|
||||||
'accounts' => $accounts,
|
'accounts' => $accounts,
|
||||||
|
|
@ -1108,7 +1109,7 @@ public function show(ClientCase $clientCase)
|
||||||
logger()->info('Show contracts balances', [
|
logger()->info('Show contracts balances', [
|
||||||
'case_id' => $case->id,
|
'case_id' => $case->id,
|
||||||
'contract_count' => $contracts->count(),
|
'contract_count' => $contracts->count(),
|
||||||
'contracts' => $contracts->map(fn($c) => [
|
'contracts' => $contracts->map(fn ($c) => [
|
||||||
'id' => $c->id,
|
'id' => $c->id,
|
||||||
'uuid' => $c->uuid,
|
'uuid' => $c->uuid,
|
||||||
'reference' => $c->reference,
|
'reference' => $c->reference,
|
||||||
|
|
@ -1330,8 +1331,6 @@ public function archiveContract(ClientCase $clientCase, string $uuid, Request $r
|
||||||
$hasReactivateRule = false;
|
$hasReactivateRule = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
$executor = app(\App\Services\Archiving\ArchiveExecutor::class);
|
$executor = app(\App\Services\Archiving\ArchiveExecutor::class);
|
||||||
$context = [
|
$context = [
|
||||||
'contract_id' => $contract->id,
|
'contract_id' => $contract->id,
|
||||||
|
|
@ -1475,4 +1474,70 @@ public function archiveContract(ClientCase $clientCase, string $uuid, Request $r
|
||||||
|
|
||||||
return back()->with('success', $message);
|
return back()->with('success', $message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emergency: recreate a missing / soft-deleted person for a client case and re-link related data.
|
||||||
|
*/
|
||||||
|
public function emergencyCreatePerson(ClientCase $clientCase, Request $request)
|
||||||
|
{
|
||||||
|
$oldPersonId = $clientCase->person_id;
|
||||||
|
/** @var \App\Models\Person\Person|null $existing */
|
||||||
|
$existing = \App\Models\Person\Person::withTrashed()->find($oldPersonId);
|
||||||
|
if ($existing && ! $existing->trashed()) {
|
||||||
|
return back()->with('flash', [
|
||||||
|
'type' => 'info',
|
||||||
|
'message' => 'Person already exists – emergency creation not needed.',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $request->validate([
|
||||||
|
'full_name' => ['nullable', 'string', 'max:255'],
|
||||||
|
'first_name' => ['nullable', 'string', 'max:255'],
|
||||||
|
'last_name' => ['nullable', 'string', 'max:255'],
|
||||||
|
'tax_number' => ['nullable', 'string', 'max:99'],
|
||||||
|
'social_security_number' => ['nullable', 'string', 'max:99'],
|
||||||
|
'description' => ['nullable', 'string', 'max:500'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$fullName = $data['full_name'] ?? trim(($data['first_name'] ?? '').' '.($data['last_name'] ?? ''));
|
||||||
|
if ($fullName === '') {
|
||||||
|
$fullName = 'Unknown Person';
|
||||||
|
}
|
||||||
|
|
||||||
|
$newPerson = null;
|
||||||
|
|
||||||
|
\DB::transaction(function () use ($oldPersonId, $clientCase, $fullName, $data, &$newPerson) {
|
||||||
|
$newPerson = \App\Models\Person\Person::create([
|
||||||
|
'nu' => null,
|
||||||
|
'first_name' => $data['first_name'] ?? null,
|
||||||
|
'last_name' => $data['last_name'] ?? null,
|
||||||
|
'full_name' => $fullName,
|
||||||
|
'gender' => null,
|
||||||
|
'birthday' => null,
|
||||||
|
'tax_number' => $data['tax_number'] ?? null,
|
||||||
|
'social_security_number' => $data['social_security_number'] ?? null,
|
||||||
|
'description' => $data['description'] ?? 'Emergency recreated person (case)',
|
||||||
|
'group_id' => 2,
|
||||||
|
'type_id' => 1,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Re-point related data referencing old person
|
||||||
|
$tables = [
|
||||||
|
'emails', 'person_phones', 'person_addresses', 'bank_accounts',
|
||||||
|
];
|
||||||
|
foreach ($tables as $table) {
|
||||||
|
\DB::table($table)->where('person_id', $oldPersonId)->update(['person_id' => $newPerson->id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the client case
|
||||||
|
$clientCase->person_id = $newPerson->id;
|
||||||
|
$clientCase->save();
|
||||||
|
});
|
||||||
|
|
||||||
|
return back()->with('flash', [
|
||||||
|
'type' => 'success',
|
||||||
|
'message' => 'New person created and case re-linked.',
|
||||||
|
'person_uuid' => $newPerson?->uuid,
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -158,4 +158,75 @@ public function update(Client $client, Request $request)
|
||||||
|
|
||||||
return to_route('client.show', $client);
|
return to_route('client.show', $client);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emergency endpoint: if the linked person record is missing (hard deleted) or soft deleted,
|
||||||
|
* create a new minimal Person and re-point all related child records (emails, phones, addresses, bank accounts,
|
||||||
|
* client cases) from the old person_id to the new one, then update the client itself.
|
||||||
|
*/
|
||||||
|
public function emergencyCreatePerson(Client $client, Request $request)
|
||||||
|
{
|
||||||
|
$oldPersonId = $client->person_id;
|
||||||
|
|
||||||
|
// If person exists and is not trashed, abort – nothing to do
|
||||||
|
/** @var \App\Models\Person\Person|null $existing */
|
||||||
|
$existing = \App\Models\Person\Person::withTrashed()->find($oldPersonId);
|
||||||
|
if ($existing && ! $existing->trashed()) {
|
||||||
|
return redirect()->back()->with('flash', [
|
||||||
|
'type' => 'info',
|
||||||
|
'message' => 'Person already exists – emergency creation not needed.',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $request->validate([
|
||||||
|
'full_name' => ['nullable', 'string', 'max:255'],
|
||||||
|
'first_name' => ['nullable', 'string', 'max:255'],
|
||||||
|
'last_name' => ['nullable', 'string', 'max:255'],
|
||||||
|
'tax_number' => ['nullable', 'string', 'max:99'],
|
||||||
|
'social_security_number' => ['nullable', 'string', 'max:99'],
|
||||||
|
'description' => ['nullable', 'string', 'max:500'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Provide sensible fallbacks.
|
||||||
|
$fullName = $data['full_name'] ?? trim(($data['first_name'] ?? '').' '.($data['last_name'] ?? ''));
|
||||||
|
if ($fullName === '') {
|
||||||
|
$fullName = 'Unknown Person';
|
||||||
|
}
|
||||||
|
|
||||||
|
$newPerson = null;
|
||||||
|
|
||||||
|
\DB::transaction(function () use ($oldPersonId, $client, $fullName, $data, &$newPerson) {
|
||||||
|
$newPerson = \App\Models\Person\Person::create([
|
||||||
|
'nu' => null, // boot event will generate
|
||||||
|
'first_name' => $data['first_name'] ?? null,
|
||||||
|
'last_name' => $data['last_name'] ?? null,
|
||||||
|
'full_name' => $fullName,
|
||||||
|
'gender' => null,
|
||||||
|
'birthday' => null,
|
||||||
|
'tax_number' => $data['tax_number'] ?? null,
|
||||||
|
'social_security_number' => $data['social_security_number'] ?? null,
|
||||||
|
'description' => $data['description'] ?? 'Emergency recreated person',
|
||||||
|
'group_id' => 1,
|
||||||
|
'type_id' => 2,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Re-point related records referencing the old (missing) person id
|
||||||
|
$tables = [
|
||||||
|
'emails', 'person_phones', 'person_addresses', 'bank_accounts', 'client_cases',
|
||||||
|
];
|
||||||
|
foreach ($tables as $table) {
|
||||||
|
\DB::table($table)->where('person_id', $oldPersonId)->update(['person_id' => $newPerson->id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally update the client itself (only this one; avoid touching other potential clients)
|
||||||
|
$client->person_id = $newPerson->id;
|
||||||
|
$client->save();
|
||||||
|
});
|
||||||
|
|
||||||
|
return redirect()->back()->with('flash', [
|
||||||
|
'type' => 'success',
|
||||||
|
'message' => 'New person created and related records re-linked.',
|
||||||
|
'person_uuid' => $newPerson?->uuid,
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,23 +3,27 @@
|
||||||
namespace App\Models\Person;
|
namespace App\Models\Person;
|
||||||
|
|
||||||
use App\Traits\Uuid;
|
use App\Traits\Uuid;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasOne;
|
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
use Laravel\Sanctum\HasApiTokens;
|
use Laravel\Sanctum\HasApiTokens;
|
||||||
use Laravel\Scout\Searchable;
|
use Laravel\Scout\Searchable;
|
||||||
|
|
||||||
class Person extends Model
|
class Person extends Model
|
||||||
{
|
{
|
||||||
use HasApiTokens;
|
use HasApiTokens;
|
||||||
|
|
||||||
/** @use HasFactory<\Database\Factories\Person/PersonFactory> */
|
/** @use HasFactory<\Database\Factories\Person/PersonFactory> */
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
use Uuid;
|
|
||||||
use Searchable;
|
use Searchable;
|
||||||
|
use SoftDeletes;
|
||||||
|
use Uuid;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The attributes that are mass assignable.
|
* The attributes that are mass assignable.
|
||||||
|
|
@ -27,6 +31,7 @@ class Person extends Model
|
||||||
* @var array<int, string>
|
* @var array<int, string>
|
||||||
*/
|
*/
|
||||||
protected $table = 'person';
|
protected $table = 'person';
|
||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'nu',
|
'nu',
|
||||||
'first_name',
|
'first_name',
|
||||||
|
|
@ -39,18 +44,19 @@ class Person extends Model
|
||||||
'description',
|
'description',
|
||||||
'group_id',
|
'group_id',
|
||||||
'type_id',
|
'type_id',
|
||||||
'user_id'
|
'user_id',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $hidden = [
|
protected $hidden = [
|
||||||
'id',
|
'id',
|
||||||
'deleted',
|
'deleted',
|
||||||
'user_id'
|
'user_id',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected static function booted(){
|
protected static function booted()
|
||||||
|
{
|
||||||
static::creating(function (Person $person) {
|
static::creating(function (Person $person) {
|
||||||
if(!isset($person->user_id)){
|
if (! isset($person->user_id)) {
|
||||||
$person->user_id = auth()->id();
|
$person->user_id = auth()->id();
|
||||||
}
|
}
|
||||||
// Ensure a unique 6-character alphanumeric 'nu' is set globally on create
|
// Ensure a unique 6-character alphanumeric 'nu' is set globally on create
|
||||||
|
|
@ -72,16 +78,15 @@ public function toSearchableArray(): array
|
||||||
'last_name' => '',
|
'last_name' => '',
|
||||||
'full_name' => '',
|
'full_name' => '',
|
||||||
'person_addresses.address' => '',
|
'person_addresses.address' => '',
|
||||||
'person_phones.nu' => ''
|
'person_phones.nu' => '',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function phones(): HasMany
|
public function phones(): HasMany
|
||||||
{
|
{
|
||||||
return $this->hasMany(\App\Models\Person\PersonPhone::class)
|
return $this->hasMany(\App\Models\Person\PersonPhone::class)
|
||||||
->with(['type'])
|
->with(['type'])
|
||||||
->where('active','=',1)
|
->where('active', '=', 1)
|
||||||
->orderBy('id');
|
->orderBy('id');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -89,7 +94,7 @@ public function addresses(): HasMany
|
||||||
{
|
{
|
||||||
return $this->hasMany(\App\Models\Person\PersonAddress::class)
|
return $this->hasMany(\App\Models\Person\PersonAddress::class)
|
||||||
->with(['type'])
|
->with(['type'])
|
||||||
->where('active','=',1)
|
->where('active', '=', 1)
|
||||||
->orderBy('id');
|
->orderBy('id');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -135,6 +140,7 @@ protected static function generateUniqueNu(): string
|
||||||
do {
|
do {
|
||||||
$nu = Str::random(6); // [A-Za-z0-9]
|
$nu = Str::random(6); // [A-Za-z0-9]
|
||||||
} while (static::where('nu', $nu)->exists());
|
} while (static::where('nu', $nu)->exists());
|
||||||
|
|
||||||
return $nu;
|
return $nu;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,18 @@ const fmtCurrency = (v) => {
|
||||||
>
|
>
|
||||||
{{ c.person?.full_name || "-" }}
|
{{ c.person?.full_name || "-" }}
|
||||||
</Link>
|
</Link>
|
||||||
|
<button
|
||||||
|
v-if="!c.person"
|
||||||
|
@click.prevent="
|
||||||
|
router.post(
|
||||||
|
route('clientCase.emergencyPerson', { client_case: c.uuid })
|
||||||
|
)
|
||||||
|
"
|
||||||
|
class="ml-2 inline-flex items-center rounded bg-red-50 px-2 py-0.5 text-xs font-semibold text-red-600 hover:bg-red-100 border border-red-200"
|
||||||
|
title="Emergency: recreate missing person"
|
||||||
|
>
|
||||||
|
Add Person
|
||||||
|
</button>
|
||||||
</td>
|
</td>
|
||||||
<td class="py-2 pr-4">{{ c.client?.person?.full_name || "-" }}</td>
|
<td class="py-2 pr-4">{{ c.client?.person?.full_name || "-" }}</td>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -141,6 +141,17 @@ const fmtCurrency = (v) => {
|
||||||
>
|
>
|
||||||
{{ client.person?.full_name || "-" }}
|
{{ client.person?.full_name || "-" }}
|
||||||
</Link>
|
</Link>
|
||||||
|
<div v-if="!client.person" class="mt-1">
|
||||||
|
<PrimaryButton
|
||||||
|
class="!py-0.5 !px-2 bg-red-500 hover:bg-red-600 text-xs"
|
||||||
|
@click.prevent="
|
||||||
|
router.post(
|
||||||
|
route('client.emergencyPerson', { uuid: client.uuid })
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>Add Person</PrimaryButton
|
||||||
|
>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="py-2 pr-4 text-right">
|
<td class="py-2 pr-4 text-right">
|
||||||
{{ client.cases_with_active_contracts_count ?? 0 }}
|
{{ client.cases_with_active_contracts_count ?? 0 }}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\Charts\ExampleChart;
|
|
||||||
use App\Http\Controllers\AccountBookingController;
|
use App\Http\Controllers\AccountBookingController;
|
||||||
use App\Http\Controllers\AccountPaymentController;
|
use App\Http\Controllers\AccountPaymentController;
|
||||||
use App\Http\Controllers\ArchiveSettingController;
|
use App\Http\Controllers\ArchiveSettingController;
|
||||||
|
|
@ -19,7 +18,6 @@
|
||||||
use App\Http\Controllers\SettingController;
|
use App\Http\Controllers\SettingController;
|
||||||
use App\Http\Controllers\WorkflowController;
|
use App\Http\Controllers\WorkflowController;
|
||||||
use App\Models\Person\Person;
|
use App\Models\Person\Person;
|
||||||
use ArielMejiaDev\LarapexCharts\LarapexChart;
|
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
use Inertia\Inertia;
|
use Inertia\Inertia;
|
||||||
|
|
@ -65,7 +63,9 @@
|
||||||
|
|
||||||
// Mail profiles (dynamic outgoing mail configuration)
|
// Mail profiles (dynamic outgoing mail configuration)
|
||||||
Route::get('mail-profiles', [\App\Http\Controllers\Admin\MailProfileController::class, 'index'])->name('mail-profiles.index');
|
Route::get('mail-profiles', [\App\Http\Controllers\Admin\MailProfileController::class, 'index'])->name('mail-profiles.index');
|
||||||
Route::get('mail-profiles.json', function() { return \App\Models\MailProfile::query()->get(); })->name('mail-profiles.json');
|
Route::get('mail-profiles.json', function () {
|
||||||
|
return \App\Models\MailProfile::query()->get();
|
||||||
|
})->name('mail-profiles.json');
|
||||||
Route::post('mail-profiles', [\App\Http\Controllers\Admin\MailProfileController::class, 'store'])->name('mail-profiles.store');
|
Route::post('mail-profiles', [\App\Http\Controllers\Admin\MailProfileController::class, 'store'])->name('mail-profiles.store');
|
||||||
Route::put('mail-profiles/{mailProfile}', [\App\Http\Controllers\Admin\MailProfileController::class, 'update'])->name('mail-profiles.update');
|
Route::put('mail-profiles/{mailProfile}', [\App\Http\Controllers\Admin\MailProfileController::class, 'update'])->name('mail-profiles.update');
|
||||||
Route::post('mail-profiles/{mailProfile}/toggle', [\App\Http\Controllers\Admin\MailProfileController::class, 'toggle'])->name('mail-profiles.toggle');
|
Route::post('mail-profiles/{mailProfile}/toggle', [\App\Http\Controllers\Admin\MailProfileController::class, 'toggle'])->name('mail-profiles.toggle');
|
||||||
|
|
@ -208,6 +208,7 @@
|
||||||
Route::get('clients/{client:uuid}', [ClientController::class, 'show'])->name('client.show');
|
Route::get('clients/{client:uuid}', [ClientController::class, 'show'])->name('client.show');
|
||||||
Route::post('clients', [ClientController::class, 'store'])->name('client.store');
|
Route::post('clients', [ClientController::class, 'store'])->name('client.store');
|
||||||
Route::put('clients/{client:uuid}', [ClientController::class, 'update'])->name('client.update');
|
Route::put('clients/{client:uuid}', [ClientController::class, 'update'])->name('client.update');
|
||||||
|
Route::post('clients/{client:uuid}/emergency-person', [ClientController::class, 'emergencyCreatePerson'])->name('client.emergencyPerson');
|
||||||
|
|
||||||
// client-case
|
// client-case
|
||||||
Route::get('client-cases', [ClientCaseContoller::class, 'index'])->name('clientCase');
|
Route::get('client-cases', [ClientCaseContoller::class, 'index'])->name('clientCase');
|
||||||
|
|
@ -215,6 +216,7 @@
|
||||||
Route::post('client-cases/{client_case:uuid}/contracts/{uuid}/segment', [ClientCaseContoller::class, 'updateContractSegment'])->name('clientCase.contract.updateSegment');
|
Route::post('client-cases/{client_case:uuid}/contracts/{uuid}/segment', [ClientCaseContoller::class, 'updateContractSegment'])->name('clientCase.contract.updateSegment');
|
||||||
Route::post('client-cases/{client_case:uuid}/contracts/{uuid}/archive', [ClientCaseContoller::class, 'archiveContract'])->name('clientCase.contract.archive');
|
Route::post('client-cases/{client_case:uuid}/contracts/{uuid}/archive', [ClientCaseContoller::class, 'archiveContract'])->name('clientCase.contract.archive');
|
||||||
Route::post('client-cases', [ClientCaseContoller::class, 'store'])->name('clientCase.store');
|
Route::post('client-cases', [ClientCaseContoller::class, 'store'])->name('clientCase.store');
|
||||||
|
Route::post('client-cases/{client_case:uuid}/emergency-person', [ClientCaseContoller::class, 'emergencyCreatePerson'])->name('clientCase.emergencyPerson');
|
||||||
// client-case / contract
|
// client-case / contract
|
||||||
Route::post('client-cases/{client_case:uuid}/contract', [ClientCaseContoller::class, 'storeContract'])->name('clientCase.contract.store');
|
Route::post('client-cases/{client_case:uuid}/contract', [ClientCaseContoller::class, 'storeContract'])->name('clientCase.contract.store');
|
||||||
Route::put('client-cases/{client_case:uuid}/contract/{uuid}', [ClientCaseContoller::class, 'updateContract'])->name('clientCase.contract.update');
|
Route::put('client-cases/{client_case:uuid}/contract/{uuid}', [ClientCaseContoller::class, 'updateContract'])->name('clientCase.contract.update');
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user