search) { $query->where(function ($q) use ($search) { $q->where('company_name', 'like', "%{$search}%") ->orWhere('contact_person', 'like', "%{$search}%") ->orWhere('email', 'like', "%{$search}%"); }); } if ($spec = $request->specialization) { $query->where('specialization', $spec); } if ($status = $request->status) { $query->where('status', $status); } $contractors = $query->withCount(['equipment', 'certifications', 'invoices', 'projects']) ->latest() ->paginate(15) ->withQueryString(); return Inertia::render('ContractorManagement::Contractors/Index', [ 'contractors' => $contractors, 'filters' => $request->only(['search', 'specialization', 'status']), ]); } public function create() { return Inertia::render('ContractorManagement::Contractors/Create'); } public function store(Request $request) { $validated = $request->validate([ 'company_name' => 'required|string|max:255', 'contact_person' => 'required|string|max:255', 'email' => 'required|email|unique:contractors,email', 'phone' => 'nullable|string|max:50', 'specialization' => 'nullable|string|max:100', 'address' => 'nullable|string', 'tax_id' => 'nullable|string|max:50', 'payment_terms' => 'required|in:net_15,net_30,net_60', 'shares_materials_catalog' => 'nullable|boolean', 'create_admin' => 'nullable|boolean', 'admin_name' => 'required_if:create_admin,true,1|nullable|string|max:255', 'admin_email' => 'required_if:create_admin,true,1|nullable|email|unique:users,email', 'admin_password' => 'required_if:create_admin,true,1|nullable|string|min:8', ]); DB::transaction(function () use ($validated, $request) { $contractorData = collect($validated)->except([ 'create_admin', 'admin_name', 'admin_email', 'admin_password' ])->toArray(); $contractorData['status'] = 'active'; $contractorData['type'] = 'main'; $contractor = Contractor::create($contractorData); if ($request->boolean('create_admin')) { $user = User::create([ 'name' => $validated['admin_name'], 'email' => $validated['admin_email'], 'password' => Hash::make($validated['admin_password']), 'user_type' => 'contractor', 'contractor_id' => $contractor->id, 'status' => 'active', 'must_change_password' => true, ]); // Provision every contractor administrator with the single supported contractor role. $roleName = 'Main Contractor Admin'; $role = Role::firstOrCreate(['name' => $roleName, 'guard_name' => 'web']); $permission = \Spatie\Permission\Models\Permission::firstOrCreate([ 'name' => 'users.access', 'guard_name' => 'web', ]); $role->givePermissionTo($permission); $user->assignRole($role); } }); return redirect()->route('contractors.index')->with('success', 'Contractor created successfully.'); } public function show(Contractor $contractor) { $contractor->load([ 'equipment.deployments.project:id,name', 'certifications', 'invoices.project:id,name', 'projects:id,name,code,status', ]); // Projects can be linked through the legacy pivot or directly through // projects.contractor_id. Show both sources in the contractor profile. $directProjects = $contractor->directProjects() ->select('id', 'ulid', 'name', 'code', 'status', 'contract_value') ->get(); $contractor->setRelation( 'projects', $contractor->projects->concat($directProjects)->unique('id')->values() ); $financialInvoices = FinancialInvoice::with('project:id,ulid,name,code') ->whereIn('project_id', $contractor->projects->pluck('id')) ->latest('invoice_date') ->get(['id', 'ulid', 'project_id', 'invoice_number', 'status', 'total_amount', 'paid_amount', 'invoice_date', 'due_date']); $projects = Project::select('id', 'ulid', 'name', 'code')->get(); $employees = User::select('id', 'ulid', 'name')->get(); return Inertia::render('ContractorManagement::Contractors/Show', [ 'contractor' => $contractor, 'projects' => $projects, 'employees' => $employees, 'financialInvoices' => $financialInvoices, ]); } public function edit(Contractor $contractor) { return Inertia::render('ContractorManagement::Contractors/Edit', [ 'contractor' => $contractor, ]); } public function update(Request $request, Contractor $contractor) { $validated = $request->validate([ 'company_name' => 'required|string|max:255', 'contact_person' => 'required|string|max:255', 'email' => "required|email|unique:contractors,email,{$contractor->id}", 'phone' => 'nullable|string|max:50', 'specialization' => 'nullable|string|max:100', 'address' => 'nullable|string', 'tax_id' => 'nullable|string|max:50', 'payment_terms' => 'required|in:net_15,net_30,net_60', 'shares_materials_catalog' => 'nullable|boolean', ]); $contractor->update($validated); return redirect()->route('contractors.show', $contractor)->with('success', 'Contractor updated.'); } public function destroy(Contractor $contractor) { $contractor->delete(); return redirect()->route('contractors.index')->with('success', 'Contractor deleted.'); } // --- Equipment --- public function storeEquipment(Request $request, Contractor $contractor) { $validated = $request->validate([ 'name' => 'required|string|max:255', 'type' => 'nullable|string|max:50', 'serial_number' => 'nullable|string|max:100', 'daily_rate' => 'nullable|numeric|min:0', ]); $contractor->equipment()->create($validated); return back()->with('success', 'Equipment added.'); } // --- Certifications --- public function storeCertification(Request $request, Contractor $contractor) { $validated = $request->validate([ 'name' => 'required|string|max:255', 'certification_number' => 'nullable|string|max:100', 'issued_date' => 'nullable|date', 'expiry_date' => 'nullable|date|after:issued_date', ]); $contractor->certifications()->create($validated); return back()->with('success', 'Certification added.'); } // --- Project Assignment --- public function assignProject(Request $request, Contractor $contractor) { $validated = $request->validate([ 'project_id' => 'required|string', 'role' => 'required|in:subcontractor,supplier,consultant', 'contract_amount' => 'nullable|numeric|min:0', ]); $projectId = Project::resolveUlidToId($validated['project_id']); $contractor->projects()->syncWithoutDetaching([ $projectId => [ 'role' => $validated['role'], 'contract_amount' => $validated['contract_amount'] ?? 0, ], ]); return back()->with('success', 'Contractor assigned to project.'); } public function removeProject(Contractor $contractor, Project $project) { $contractor->projects()->detach($project->id); return back()->with('success', 'Contractor removed from project.'); } // --- Invoices --- public function storeInvoice(Request $request, Contractor $contractor) { $validated = $request->validate([ 'project_id' => 'nullable|string', 'invoice_number' => 'required|string|unique:contractor_invoices', 'amount' => 'required|numeric|min:0.01', 'description' => 'nullable|string', 'invoice_date' => 'required|date', 'due_date' => 'nullable|date', ]); // Resolve ULID → ID for project if (!empty($validated['project_id'])) { $validated['project_id'] = Project::resolveUlidToId($validated['project_id']); } $contractor->invoices()->create($validated); return back()->with('success', 'Invoice created.'); } public function submitInvoice(ContractorInvoice $invoice) { try { $invoice->transitionTo(InvoiceStatus::Submitted); ContractorInvoiceSubmitted::dispatch($invoice); } catch (\InvalidArgumentException $e) { return back()->with('error', $e->getMessage()); } return back()->with('success', 'Invoice submitted for approval.'); } public function approveInvoice(ContractorInvoice $invoice) { $user = auth()->user(); $isHigherUp = $user->user_type === 'admin' || $user->roles()->whereIn('name', ['Super Admin', 'admin', 'Main Contractor Admin'])->exists(); if (!$isHigherUp) { return back()->with('error', 'Unauthorized. Only Main Contractor Admin or Super Admin can approve subcontractor invoices.'); } try { $invoice->transitionTo(InvoiceStatus::Approved); } catch (\InvalidArgumentException $e) { return back()->with('error', $e->getMessage()); } return back()->with('success', 'Invoice approved.'); } public function payInvoice(Request $request, ContractorInvoice $invoice) { $validated = $request->validate([ 'amount' => 'required|numeric|min:0.01', 'payment_method' => 'required|in:bank_transfer,check,cash', 'reference_number' => 'nullable|string', 'payment_date' => 'required|date', ]); $invoice->payments()->create($validated); try { $invoice->transitionTo(InvoiceStatus::Paid); ContractorInvoicePaid::dispatch($invoice); } catch (\InvalidArgumentException $e) { return back()->with('error', $e->getMessage()); } return back()->with('success', 'Payment recorded and invoice marked as paid.'); } }