feat: implement core ERP modules, multi-tenant architecture, and comprehensive system workflow documentation.
This commit is contained in:
@@ -20,12 +20,18 @@ class MaterialRequisitionController extends Controller
|
||||
public function index(Request $request)
|
||||
{
|
||||
$query = MaterialRequisition::with([
|
||||
'requester:id,ulid,name',
|
||||
'approver:id,ulid,name',
|
||||
// The requisition is already tenant/project-scoped. Load the
|
||||
// display names without User's tenant scope so a valid requester
|
||||
// from the assigned project is not serialized as null.
|
||||
'requester' => fn ($userQuery) => $userQuery->withoutGlobalScopes()->select('id', 'ulid', 'name'),
|
||||
'approver' => fn ($userQuery) => $userQuery->withoutGlobalScopes()->select('id', 'ulid', 'name'),
|
||||
'project:id,ulid,name,code',
|
||||
'items:id,material_requisition_id,quantity,unit_cost'
|
||||
]);
|
||||
|
||||
// Requisitions inherit visibility from their projects.
|
||||
$query->whereIn('project_id', $this->availableProjectsQuery()->select('projects.id'));
|
||||
|
||||
if ($request->filled('project_ulid')) {
|
||||
$project = Project::where('ulid', $request->project_ulid)->first();
|
||||
if ($project) {
|
||||
@@ -47,7 +53,8 @@ class MaterialRequisitionController extends Controller
|
||||
$prefilledItems = [];
|
||||
|
||||
if ($request->filled('project_ulid')) {
|
||||
$selectedProject = Project::where('ulid', $request->project_ulid)
|
||||
$selectedProject = $this->availableProjectsQuery()
|
||||
->where('ulid', $request->project_ulid)
|
||||
->with('materialsEstimates.material:id,ulid,name,unit,unit_cost')
|
||||
->firstOrFail();
|
||||
|
||||
@@ -97,7 +104,12 @@ class MaterialRequisitionController extends Controller
|
||||
return Inertia::render('MaterialLogistics::Requisitions/Form', [
|
||||
'materials' => $materials,
|
||||
'materialGroups' => $materialGroups,
|
||||
'projects' => Project::active()->with('contractor:id,company_name')->select('id', 'ulid', 'name', 'code', 'contractor_id', 'client_name', 'location', 'start_date', 'target_end_date', 'status', 'contract_value')->get(),
|
||||
'projects' => $this->availableProjectsQuery()
|
||||
->active()
|
||||
->where('current_wizard_step', '>=', 8)
|
||||
->with('contractor:id,company_name')
|
||||
->select('id', 'ulid', 'name', 'code', 'contractor_id', 'client_name', 'location', 'start_date', 'target_end_date', 'status', 'contract_value')
|
||||
->get(),
|
||||
'selectedProject' => $selectedProject,
|
||||
'prefilledItems' => $prefilledItems,
|
||||
]);
|
||||
@@ -114,7 +126,9 @@ class MaterialRequisitionController extends Controller
|
||||
'items.*.unit_cost' => 'required|numeric|min:0',
|
||||
]);
|
||||
|
||||
$project = Project::where('ulid', $validated['project_ulid'])->firstOrFail();
|
||||
$project = $this->availableProjectsQuery()
|
||||
->where('ulid', $validated['project_ulid'])
|
||||
->firstOrFail();
|
||||
if ($project->current_wizard_step < 7) {
|
||||
return redirect()->back()->with('error', 'Cannot create material requisition: Project estimation is not submitted or approved.');
|
||||
}
|
||||
@@ -197,7 +211,11 @@ class MaterialRequisitionController extends Controller
|
||||
'requisition' => $requisition,
|
||||
'materials' => $materials,
|
||||
'materialGroups' => $materialGroups,
|
||||
'projects' => Project::active()->where('current_wizard_step', '>=', 8)->select('id', 'ulid', 'name', 'code', 'client_name', 'location', 'start_date', 'target_end_date', 'status', 'contract_value')->get(),
|
||||
'projects' => $this->availableProjectsQuery()
|
||||
->active()
|
||||
->where('current_wizard_step', '>=', 8)
|
||||
->select('id', 'ulid', 'name', 'code', 'client_name', 'location', 'start_date', 'target_end_date', 'status', 'contract_value')
|
||||
->get(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -216,7 +234,9 @@ class MaterialRequisitionController extends Controller
|
||||
'items.*.unit_cost' => 'required|numeric|min:0',
|
||||
]);
|
||||
|
||||
$project = Project::where('ulid', $validated['project_ulid'])->firstOrFail();
|
||||
$project = $this->availableProjectsQuery()
|
||||
->where('ulid', $validated['project_ulid'])
|
||||
->firstOrFail();
|
||||
if ($project->current_wizard_step < 8) {
|
||||
return redirect()->back()->with('error', 'Cannot update material requisition: Project estimation is not approved.');
|
||||
}
|
||||
@@ -279,8 +299,27 @@ class MaterialRequisitionController extends Controller
|
||||
return back()->with('error', 'Only draft requisitions can be submitted.');
|
||||
}
|
||||
|
||||
// Find personnel with approve_mr permission across the whole system
|
||||
$approverIds = \App\Models\User::permission('approve_mr')
|
||||
// Material Requests may be approved by an explicit permission or by
|
||||
// the system's Project Manager/executive roles. Query without the
|
||||
// tenant scope so a site user can submit a request for an assigned
|
||||
// project even when the approver belongs to another tenant context.
|
||||
$approverIds = \App\Models\User::withoutGlobalScopes()
|
||||
->where(function ($query) {
|
||||
$query->whereHas('roles', function ($roleQuery) {
|
||||
$roleQuery->whereIn('name', [
|
||||
'Project Manager',
|
||||
'project_manager',
|
||||
'Super Admin',
|
||||
'admin',
|
||||
'Main Contractor Admin',
|
||||
]);
|
||||
})
|
||||
->orWhere('user_type', 'admin')
|
||||
->orWhereHas('permissions', function ($permissionQuery) {
|
||||
$permissionQuery->where('name', 'approve_mr');
|
||||
});
|
||||
})
|
||||
->where('status', 'active')
|
||||
->pluck('id')
|
||||
->values()
|
||||
->toArray();
|
||||
@@ -341,4 +380,45 @@ class MaterialRequisitionController extends Controller
|
||||
|
||||
return $pdf->download("MR-{$requisition->document_number}.pdf");
|
||||
}
|
||||
|
||||
private function availableProjectsQuery()
|
||||
{
|
||||
$query = Project::withoutGlobalScope(\App\Scopes\TenantScope::class);
|
||||
$user = auth()->user();
|
||||
$isSiteOperationsUser = $user && $user->hasAnyRole([
|
||||
'Site Technical',
|
||||
'Construction Supervisor',
|
||||
'Site Operations',
|
||||
'Site Engineer',
|
||||
'Site Supervisor',
|
||||
]);
|
||||
|
||||
if ($user?->contractor_id) {
|
||||
$contractorIds = DB::table('contractors')
|
||||
->where('id', $user->contractor_id)
|
||||
->orWhere('parent_id', $user->contractor_id)
|
||||
->pluck('id')
|
||||
->all();
|
||||
|
||||
$query->where(function ($projectQuery) use ($contractorIds, $user, $isSiteOperationsUser) {
|
||||
$projectQuery
|
||||
->whereIn('projects.contractor_id', $contractorIds)
|
||||
->orWhereHas('contractors', function ($contractorQuery) use ($contractorIds) {
|
||||
$contractorQuery->whereIn('contractors.id', $contractorIds);
|
||||
});
|
||||
|
||||
if ($isSiteOperationsUser) {
|
||||
$projectQuery->orWhereHas('personnel', function ($personnelQuery) use ($user) {
|
||||
$personnelQuery->where('users.id', $user->id);
|
||||
});
|
||||
}
|
||||
});
|
||||
} elseif ($isSiteOperationsUser) {
|
||||
$query->whereHas('personnel', function ($personnelQuery) use ($user) {
|
||||
$personnelQuery->where('users.id', $user->id);
|
||||
});
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user