Changes 0228092025 Laptop

This commit is contained in:
2025-09-28 14:51:02 +02:00
parent 765beb78b7
commit b40ee9dcde
36 changed files with 2099 additions and 65 deletions
+91 -5
View File
@@ -10,6 +10,8 @@
use Exception;
use Illuminate\Database\QueryException;
use Illuminate\Http\Request;
use App\Http\Requests\StoreContractRequest;
use App\Http\Requests\UpdateContractRequest;
use Inertia\Inertia;
class ClientCaseContoller extends Controller
@@ -93,7 +95,7 @@ public function store(Request $request)
return to_route('client.show', $client);
}
public function storeContract(ClientCase $clientCase, Request $request)
public function storeContract(ClientCase $clientCase, StoreContractRequest $request)
{
\DB::transaction(function() use ($request, $clientCase){
@@ -106,6 +108,8 @@ public function storeContract(ClientCase $clientCase, Request $request)
'description' => $request->input('description'),
]);
// Note: Contract config auto-application is handled in Contract model created hook.
// Optionally create/update related account amounts
$initial = $request->input('initial_amount');
$balance = $request->input('balance_amount');
@@ -121,7 +125,7 @@ public function storeContract(ClientCase $clientCase, Request $request)
return to_route('clientCase.show', $clientCase);
}
public function updateContract(ClientCase $clientCase, String $uuid, Request $request)
public function updateContract(ClientCase $clientCase, String $uuid, UpdateContractRequest $request)
{
$contract = Contract::where('uuid', $uuid)->firstOrFail();
@@ -209,6 +213,86 @@ public function deleteContract(ClientCase $clientCase, String $uuid, Request $re
return to_route('clientCase.show', $clientCase);
}
public function updateContractSegment(ClientCase $clientCase, string $uuid, Request $request)
{
$validated = $request->validate([
'segment_id' => ['required', 'integer', 'exists:segments,id'],
]);
$contract = $clientCase->contracts()->where('uuid', $uuid)->firstOrFail();
\DB::transaction(function () use ($contract, $validated) {
// Deactivate current active relation(s)
\DB::table('contract_segment')
->where('contract_id', $contract->id)
->where('active', true)
->update(['active' => false]);
// Attach or update the selected segment as active
$existing = \DB::table('contract_segment')
->where('contract_id', $contract->id)
->where('segment_id', $validated['segment_id'])
->first();
if ($existing) {
\DB::table('contract_segment')
->where('id', $existing->id)
->update(['active' => true, 'updated_at' => now()]);
} else {
$contract->segments()->attach($validated['segment_id'], ['active' => true, 'created_at' => now(), 'updated_at' => now()]);
}
});
return back()->with('success', 'Contract segment updated.');
}
public function attachSegment(ClientCase $clientCase, Request $request)
{
$validated = $request->validate([
'segment_id' => ['required', 'integer', 'exists:segments,id'],
'contract_uuid' => ['nullable', 'uuid'],
'make_active_for_contract' => ['sometimes', 'boolean'],
]);
\DB::transaction(function () use ($clientCase, $validated) {
// Attach segment to client case if not already attached
$attached = \DB::table('client_case_segment')
->where('client_case_id', $clientCase->id)
->where('segment_id', $validated['segment_id'])
->first();
if (!$attached) {
$clientCase->segments()->attach($validated['segment_id'], ['active' => true]);
} else if (!$attached->active) {
\DB::table('client_case_segment')
->where('id', $attached->id)
->update(['active' => true, 'updated_at' => now()]);
}
// Optionally make it active for a specific contract
if (!empty($validated['contract_uuid']) && ($validated['make_active_for_contract'] ?? false)) {
$contract = $clientCase->contracts()->where('uuid', $validated['contract_uuid'])->firstOrFail();
\DB::table('contract_segment')
->where('contract_id', $contract->id)
->where('active', true)
->update(['active' => false]);
$existing = \DB::table('contract_segment')
->where('contract_id', $contract->id)
->where('segment_id', $validated['segment_id'])
->first();
if ($existing) {
\DB::table('contract_segment')
->where('id', $existing->id)
->update(['active' => true, 'updated_at' => now()]);
} else {
$contract->segments()->attach($validated['segment_id'], ['active' => true, 'created_at' => now(), 'updated_at' => now()]);
}
}
});
return back()->with('success', 'Segment attached to case.');
}
public function storeDocument(ClientCase $clientCase, Request $request)
{
$validated = $request->validate([
@@ -336,11 +420,11 @@ public function show(ClientCase $clientCase)
'phone_types' => \App\Models\Person\PhoneType::all()
];
return Inertia::render('Cases/Show', [
return Inertia::render('Cases/Show', [
'client' => $case->client()->with('person', fn($q) => $q->with(['addresses', 'phones']))->firstOrFail(),
'client_case' => $case,
'contracts' => $case->contracts()
->with(['type', 'account', 'objects'])
->with(['type', 'account', 'objects', 'segments:id,name'])
->orderByDesc('created_at')->get(),
'activities' => $case->activities()->with(['action', 'decision', 'contract:id,uuid,reference'])
->orderByDesc('created_at')
@@ -348,7 +432,9 @@ public function show(ClientCase $clientCase)
'documents' => $case->documents()->orderByDesc('created_at')->get(),
'contract_types' => \App\Models\ContractType::whereNull('deleted_at')->get(),
'actions' => \App\Models\Action::with('decisions')->get(),
'types' => $types
'types' => $types,
'segments' => $case->segments()->wherePivot('active', true)->get(['segments.id','segments.name']),
'all_segments' => \App\Models\Segment::query()->where('active', true)->get(['id','name'])
]);
}
@@ -0,0 +1,72 @@
<?php
namespace App\Http\Controllers;
use App\Models\ContractConfig;
use App\Models\ContractType;
use App\Models\Segment;
use Illuminate\Http\Request;
use Inertia\Inertia;
class ContractConfigController extends Controller
{
public function index()
{
return Inertia::render('Settings/ContractConfigs/Index', [
'configs' => ContractConfig::with(['type:id,name', 'segment:id,name'])->get(),
'types' => ContractType::query()->get(['id','name']),
'segments' => Segment::query()->where('active', true)->get(['id','name']),
]);
}
public function store(Request $request)
{
$data = $request->validate([
'contract_type_id' => ['required', 'integer', 'exists:contract_types,id'],
'segment_id' => ['required', 'integer', 'exists:segments,id'],
'is_initial' => ['sometimes', 'boolean'],
'active' => ['sometimes', 'boolean'],
]);
// Prevent duplicates for same type/segment
$exists = ContractConfig::query()
->where('contract_type_id', $data['contract_type_id'])
->where('segment_id', $data['segment_id'])
->exists();
if ($exists) {
return back()->withErrors(['segment_id' => 'This segment is already configured for the selected type.']);
}
ContractConfig::create([
'contract_type_id' => $data['contract_type_id'],
'segment_id' => $data['segment_id'],
'is_initial' => (bool)($data['is_initial'] ?? false),
'active' => (bool)($data['active'] ?? true),
]);
return back()->with('success', 'Configuration created');
}
public function update(ContractConfig $config, Request $request)
{
$data = $request->validate([
'segment_id' => ['required', 'integer', 'exists:segments,id'],
'is_initial' => ['sometimes', 'boolean'],
'active' => ['sometimes', 'boolean'],
]);
$config->update([
'segment_id' => $data['segment_id'],
'is_initial' => (bool)($data['is_initial'] ?? $config->is_initial),
'active' => (bool)($data['active'] ?? $config->active),
]);
return back()->with('success', 'Configuration updated');
}
public function destroy(ContractConfig $config)
{
$config->delete();
return back()->with('success', 'Configuration deleted');
}
}
@@ -3,6 +3,9 @@
namespace App\Http\Controllers;
use App\Models\FieldJobSetting;
use App\Models\Segment;
use App\Models\Decision;
use App\Http\Requests\StoreFieldJobSettingRequest;
use Illuminate\Http\Request;
use Inertia\Inertia;
@@ -11,11 +14,27 @@ class FieldJobSettingController extends Controller
public function index(Request $request)
{
$settings = FieldJobSetting::query()
->with(['segment', 'asignDecision', 'completeDecision'])
->with(['segment', 'initialDecision', 'asignDecision', 'completeDecision'])
->get();
return Inertia::render('Settings/FieldJob/Index', [
'settings' => $settings,
'segments' => Segment::query()->get(),
'decisions' => Decision::query()->get(),
]);
}
public function store(StoreFieldJobSettingRequest $request)
{
$attributes = $request->validated();
FieldJobSetting::create([
'segment_id' => $attributes['segment_id'],
'initial_decision_id' => $attributes['initial_decision_id'],
'asign_decision_id' => $attributes['asign_decision_id'],
'complete_decision_id' => $attributes['complete_decision_id'],
]);
return to_route('settings.fieldjob.index')->with('success', 'Field job setting created successfully!');
}
}
@@ -3,6 +3,8 @@
namespace App\Http\Controllers;
use App\Models\Segment;
use App\Http\Requests\StoreSegmentRequest;
use App\Http\Requests\UpdateSegmentRequest;
use Illuminate\Http\Request;
use Inertia\Inertia;
@@ -14,5 +16,29 @@ public function settings(Request $request)
'segments' => Segment::query()->get(),
]);
}
public function store(StoreSegmentRequest $request)
{
$data = $request->validated();
Segment::create([
'name' => $data['name'],
'description' => $data['description'] ?? null,
'active' => $data['active'] ?? true,
]);
return to_route('settings.segments')->with('success', 'Segment created');
}
public function update(UpdateSegmentRequest $request, Segment $segment)
{
$data = $request->validated();
$segment->update([
'name' => $data['name'],
'description' => $data['description'] ?? null,
'active' => $data['active'] ?? $segment->active,
]);
return to_route('settings.segments')->with('success', 'Segment updated');
}
}
+30 -2
View File
@@ -14,8 +14,8 @@ class WorkflowController extends Controller
public function index(Request $request)
{
return Inertia::render('Settings/Workflow/Index', [
'actions' => Action::query()->with(['decisions', 'segment'])->get(),
'decisions' => Decision::query()->with('actions')->get(),
'actions' => Action::query()->with(['decisions', 'segment'])->withCount('activities')->get(),
'decisions' => Decision::query()->with('actions')->withCount('activities')->get(),
'segments' => Segment::query()->get(),
]);
}
@@ -127,4 +127,32 @@ public function updateDecision(int $id, Request $request)
return to_route('settings.workflow')->with('success', 'Decision updated successfully!');
}
public function destroyAction(int $id)
{
$row = Action::findOrFail($id);
if ($row->activities()->exists()) {
return back()->with('error', 'Cannot delete action because dependent activities exist.');
}
\DB::transaction(function () use ($row) {
$row->decisions()->detach();
$row->delete();
});
return back()->with('success', 'Action deleted successfully!');
}
public function destroyDecision(int $id)
{
$row = Decision::findOrFail($id);
if ($row->activities()->exists()) {
return back()->with('error', 'Cannot delete decision because dependent activities exist.');
}
\DB::transaction(function () use ($row) {
$row->actions()->detach();
$row->delete();
});
return back()->with('success', 'Decision deleted successfully!');
}
}
@@ -0,0 +1,37 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class StoreContractRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
$clientCase = $this->route('client_case');
return [
'reference' => [
'nullable',
'string',
'max:125',
Rule::unique('contracts', 'reference')
->where(fn ($q) => $q
->where('client_case_id', optional($clientCase)->id)
->whereNull('deleted_at')
),
],
'start_date' => ['required', 'date'],
'type_id' => ['required', 'integer', 'exists:contract_types,id'],
'description' => ['nullable', 'string', 'max:255'],
'initial_amount' => ['nullable', 'numeric'],
'balance_amount' => ['nullable', 'numeric'],
];
}
}
@@ -0,0 +1,33 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StoreFieldJobSettingRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'segment_id' => ['required', 'integer', 'exists:segments,id'],
'initial_decision_id' => ['required', 'integer', 'exists:decisions,id'],
'asign_decision_id' => ['required', 'integer', 'exists:decisions,id'],
'complete_decision_id' => ['required', 'integer', 'exists:decisions,id'],
];
}
public function messages(): array
{
return [
'segment_id.required' => 'Segment is required.',
'initial_decision_id.required' => 'Initial decision is required.',
'asign_decision_id.required' => 'Assign decision is required.',
'complete_decision_id.required' => 'Complete decision is required.',
];
}
}
+29
View File
@@ -0,0 +1,29 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StoreSegmentRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:50'],
'description' => ['nullable', 'string', 'max:255'],
'active' => ['boolean'],
];
}
public function messages(): array
{
return [
'name.required' => 'Name is required.',
];
}
}
@@ -0,0 +1,39 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class UpdateContractRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
$clientCase = $this->route('client_case');
$uuid = $this->route('uuid');
return [
'reference' => [
'nullable',
'string',
'max:125',
Rule::unique('contracts', 'reference')
->where(fn ($q) => $q
->where('client_case_id', optional($clientCase)->id)
->whereNull('deleted_at')
->where('uuid', '!=', $uuid)
),
],
'start_date' => ['sometimes', 'date'],
'type_id' => ['required', 'integer', 'exists:contract_types,id'],
'description' => ['nullable', 'string', 'max:255'],
'initial_amount' => ['nullable', 'numeric'],
'balance_amount' => ['nullable', 'numeric'],
];
}
}
@@ -0,0 +1,29 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class UpdateSegmentRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:50'],
'description' => ['nullable', 'string', 'max:255'],
'active' => ['boolean'],
];
}
public function messages(): array
{
return [
'name.required' => 'Name is required.',
];
}
}
+6
View File
@@ -6,6 +6,7 @@
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Laravel\Scout\Searchable;
class Action extends Model
@@ -26,4 +27,9 @@ public function segment(): BelongsTo
return $this->belongsTo(\App\Models\Segment::class);
}
public function activities(): HasMany
{
return $this->hasMany(\App\Models\Activity::class);
}
}
+68
View File
@@ -65,4 +65,72 @@ public function objects(): HasMany
return $this->hasMany(\App\Models\CaseObject::class, 'contract_id');
}
protected static function booted(): void
{
static::created(function (Contract $contract): void {
// Only apply configs when type and client case are set and no segments are already attached
if (empty($contract->type_id) || empty($contract->client_case_id)) {
return;
}
$existing = \DB::table('contract_segment')
->where('contract_id', $contract->id)
->count();
if ($existing > 0) {
// Respect pre-attached segments (e.g. custom import logic)
return;
}
$configs = ContractConfig::query()
->where('contract_type_id', $contract->type_id)
->where('active', true)
->get(['segment_id', 'is_initial']);
if ($configs->isEmpty()) {
return;
}
foreach ($configs as $cfg) {
// Ensure the segment is attached to the client case and active
$attached = \DB::table('client_case_segment')
->where('client_case_id', $contract->client_case_id)
->where('segment_id', $cfg->segment_id)
->first();
if (!$attached) {
\DB::table('client_case_segment')->insert([
'client_case_id' => $contract->client_case_id,
'segment_id' => $cfg->segment_id,
'active' => true,
'created_at' => now(),
'updated_at' => now(),
]);
} elseif (!$attached->active) {
\DB::table('client_case_segment')
->where('id', $attached->id)
->update(['active' => true, 'updated_at' => now()]);
}
// Attach to contract; mark active if initial, otherwise inactive
$pivot = \DB::table('contract_segment')
->where('contract_id', $contract->id)
->where('segment_id', $cfg->segment_id)
->first();
$activeFlag = $cfg->is_initial ? true : false;
if ($pivot) {
\DB::table('contract_segment')
->where('id', $pivot->id)
->update(['active' => $activeFlag, 'updated_at' => now()]);
} else {
\DB::table('contract_segment')->insert([
'contract_id' => $contract->id,
'segment_id' => $cfg->segment_id,
'active' => $activeFlag,
'created_at' => now(),
'updated_at' => now(),
]);
}
}
});
}
}
+37
View File
@@ -0,0 +1,37 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class ContractConfig extends Model
{
use HasFactory;
protected $fillable = [
'contract_type_id',
'segment_id',
'is_initial',
'active',
];
protected function casts(): array
{
return [
'active' => 'boolean',
'is_initial' => 'boolean',
];
}
public function type(): BelongsTo
{
return $this->belongsTo(ContractType::class, 'contract_type_id');
}
public function segment(): BelongsTo
{
return $this->belongsTo(Segment::class, 'segment_id');
}
}
+5
View File
@@ -24,4 +24,9 @@ public function events(): BelongsToMany
{
return $this->belongsToMany(\App\Models\Event::class);
}
public function activities(): HasMany
{
return $this->hasMany(\App\Models\Activity::class);
}
}
+25 -4
View File
@@ -5,21 +5,32 @@
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes;
class FieldJob extends Model
{
use HasFactory;
use SoftDeletes;
protected $fillable = [
'field_job_setting_id',
'asigned_user_id',
'start_date',
'end_date',
'user_id',
'contract_id',
'assigned_at',
'completed_at',
'cancelled_at',
'priority',
'notes',
'address_snapshot ',
];
protected $casts = [
'start_date' => 'date',
'end_date' => 'date',
'assigned_at' => 'date',
'completed_at' => 'date',
'cancelled_at' => 'date',
'priority' => 'boolean',
'address_snapshot ' => 'array',
];
protected static function booted(){
@@ -39,4 +50,14 @@ public function assignedUser(): BelongsTo
{
return $this->belongsTo(User::class, 'asigned_user_id');
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class, 'user_id');
}
public function contract(): BelongsTo
{
return $this->belongsTo(Contract::class, 'contract_id');
}
}
+6
View File
@@ -13,6 +13,7 @@ class FieldJobSetting extends Model
protected $fillable = [
'segment_id',
'initial_decision_id',
'asign_decision_id',
'complete_decision_id',
];
@@ -27,6 +28,11 @@ public function asignDecision(): BelongsTo
return $this->belongsTo(Decision::class, 'asign_decision_id');
}
public function initialDecision(): BelongsTo
{
return $this->belongsTo(Decision::class, 'initial_decision_id');
}
public function completeDecision(): BelongsTo
{
return $this->belongsTo(Decision::class, 'complete_decision_id');
+13
View File
@@ -11,6 +11,19 @@ class Segment extends Model
/** @use HasFactory<\Database\Factories\SegmentFactory> */
use HasFactory;
protected $fillable = [
'name',
'description',
'active',
];
protected function casts(): array
{
return [
'active' => 'boolean',
];
}
public function contracts(): BelongsToMany {
return $this->belongsToMany(\App\Models\Contract::class);
}