diff --git a/Modules/ApprovalWorkflow/app/Http/Controllers/ApprovalController.php b/Modules/ApprovalWorkflow/app/Http/Controllers/ApprovalController.php index f412e71..3366d95 100644 --- a/Modules/ApprovalWorkflow/app/Http/Controllers/ApprovalController.php +++ b/Modules/ApprovalWorkflow/app/Http/Controllers/ApprovalController.php @@ -26,7 +26,7 @@ class ApprovalController extends Controller $tab = $request->get('tab', 'pending'); $isAdmin = $user->user_type === 'admin' || $user->hasRole(['Super Admin', 'admin', 'Main Contractor Admin']); - $isApprovingRole = $isAdmin || $user->hasRole(['Project Manager', 'Contractor Admin']); + $isApprovingRole = $isAdmin || $user->hasRole(['Project Manager', 'Main Contractor Admin']); $query = ApprovalChain::query(); diff --git a/Modules/ContractorManagement/app/Http/Controllers/ContractorController.php b/Modules/ContractorManagement/app/Http/Controllers/ContractorController.php index 9741ad1..a77ecf0 100644 --- a/Modules/ContractorManagement/app/Http/Controllers/ContractorController.php +++ b/Modules/ContractorManagement/app/Http/Controllers/ContractorController.php @@ -93,12 +93,8 @@ class ContractorController extends Controller 'must_change_password' => true, ]); - // Provision the account with the role that owns user-management - // actions. Keep the legacy Contractor role only for existing - // accounts; new contractor admins must use the admin role. - $roleName = $contractor->parent_id - ? 'Sub Contractor Admin' - : 'Main Contractor Admin'; + // Provision every contractor administrator with the single supported contractor role. + $roleName = 'Main Contractor Admin'; $role = Role::firstOrCreate(['name' => $roleName]); $role->givePermissionTo('users.access'); $user->assignRole($role); diff --git a/Modules/DocumentManagement/app/Policies/DocumentPolicy.php b/Modules/DocumentManagement/app/Policies/DocumentPolicy.php index 25dba49..f5c6e56 100644 --- a/Modules/DocumentManagement/app/Policies/DocumentPolicy.php +++ b/Modules/DocumentManagement/app/Policies/DocumentPolicy.php @@ -15,9 +15,9 @@ class DocumentPolicy */ public function approve(User $user, Document $document): bool { - // Strict restriction: only users with 'Contractor Admin' user_type or specific roles + // Strict restriction: only users with the contractor-admin role. // Here we assume the Contractor Admin is denoted by a specific attribute or role. - return $user->user_type === 'Contractor Admin' || $user->hasRole('Contractor Admin'); + return $user->hasRole('Main Contractor Admin'); } /** diff --git a/Modules/DocumentManagement/resources/js/Pages/Documents/Index.tsx b/Modules/DocumentManagement/resources/js/Pages/Documents/Index.tsx index e830226..2e3415f 100644 --- a/Modules/DocumentManagement/resources/js/Pages/Documents/Index.tsx +++ b/Modules/DocumentManagement/resources/js/Pages/Documents/Index.tsx @@ -376,7 +376,7 @@ export default function Index({ documents, categories, projects, filters }: Prop const [viewMode, setViewMode] = useState<'gallery' | 'list'>('gallery'); const [previewDoc, setPreviewDoc] = useState(null); - const isContractorAdmin = (auth.user as any).user_type === 'Contractor Admin'; + const isContractorAdmin = auth.roles?.includes('Main Contractor Admin'); const form = useForm<{ title: string; category_id: string; project_id: string; description: string; file: File | null }>({ title: '', category_id: '', project_id: '', description: '', file: null, diff --git a/Modules/MaterialLogistics/app/Http/Controllers/MaterialController.php b/Modules/MaterialLogistics/app/Http/Controllers/MaterialController.php index 5f3363e..b4437c8 100644 --- a/Modules/MaterialLogistics/app/Http/Controllers/MaterialController.php +++ b/Modules/MaterialLogistics/app/Http/Controllers/MaterialController.php @@ -148,7 +148,7 @@ class MaterialController extends Controller ->where(function ($q) { $q->whereIn('user_type', ['admin']) ->orWhereHas('roles', function ($rq) { - $rq->whereIn('name', ['Project Manager', 'Main Contractor Admin', 'Super Admin', 'admin', 'Construction Supervisor', 'Site Technical', 'Contractor']); + $rq->whereIn('name', ['Project Manager', 'Main Contractor Admin', 'Super Admin', 'admin', 'Construction Supervisor', 'Site Technical']); }); }) ->select('id', 'name', 'email') diff --git a/Modules/ProjectManagement/app/Http/Controllers/ProjectController.php b/Modules/ProjectManagement/app/Http/Controllers/ProjectController.php index a9078ff..6fa9fc2 100644 --- a/Modules/ProjectManagement/app/Http/Controllers/ProjectController.php +++ b/Modules/ProjectManagement/app/Http/Controllers/ProjectController.php @@ -81,7 +81,7 @@ class ProjectController extends Controller ->where(function ($q) { $q->whereIn('user_type', ['admin']) ->orWhereHas('roles', function ($rq) { - $rq->whereIn('name', ['Project Manager', 'Main Contractor Admin', 'Super Admin', 'admin', 'Contractor']); + $rq->whereIn('name', ['Project Manager', 'Main Contractor Admin', 'Super Admin', 'admin', 'Construction Supervisor', 'Site Technical']); }); }) ->with('employeeProfile') @@ -257,7 +257,7 @@ class ProjectController extends Controller ->where(function ($q) { $q->whereIn('user_type', ['admin']) ->orWhereHas('roles', function ($rq) { - $rq->whereIn('name', ['Project Manager', 'Main Contractor Admin', 'Super Admin', 'admin', 'Contractor']); + $rq->whereIn('name', ['Project Manager', 'Main Contractor Admin', 'Super Admin', 'admin', 'Construction Supervisor', 'Site Technical']); }); }) ->with('employeeProfile') @@ -437,7 +437,7 @@ class ProjectController extends Controller ->where(function ($q) { $q->whereIn('user_type', ['admin']) ->orWhereHas('roles', function ($rq) { - $rq->whereIn('name', ['Project Manager', 'Main Contractor Admin', 'Super Admin', 'admin', 'Contractor']); + $rq->whereIn('name', ['Project Manager', 'Main Contractor Admin', 'Super Admin', 'admin', 'Construction Supervisor', 'Site Technical']); }); }) ->with(['employeeProfile', 'roles']) diff --git a/Modules/ProjectManagement/resources/js/Pages/Projects/Modules/Documents.tsx b/Modules/ProjectManagement/resources/js/Pages/Projects/Modules/Documents.tsx index ef9c821..0ac28b3 100644 --- a/Modules/ProjectManagement/resources/js/Pages/Projects/Modules/Documents.tsx +++ b/Modules/ProjectManagement/resources/js/Pages/Projects/Modules/Documents.tsx @@ -350,7 +350,7 @@ export default function Documents({ project, currentTab, documents, categories, const [viewMode, setViewMode] = useState<'gallery' | 'list'>('gallery'); const [previewDoc, setPreviewDoc] = useState<{ url: string; title: string } | null>(null); - const isContractorAdmin = (auth.user as any).user_type === 'Contractor Admin'; + const isContractorAdmin = auth.roles?.includes('Main Contractor Admin'); const form = useForm<{ title: string; category_id: string; project_id: string; description: string; file: File | null }>({ title: '', category_id: '', project_id: project.id.toString(), description: '', file: null, @@ -661,4 +661,3 @@ export default function Documents({ project, currentTab, documents, categories, ); } - diff --git a/Modules/RolesPermissions/app/Http/Controllers/RolesPermissionsController.php b/Modules/RolesPermissions/app/Http/Controllers/RolesPermissionsController.php index 67c692e..fea9c0e 100644 --- a/Modules/RolesPermissions/app/Http/Controllers/RolesPermissionsController.php +++ b/Modules/RolesPermissions/app/Http/Controllers/RolesPermissionsController.php @@ -12,7 +12,7 @@ class RolesPermissionsController extends Controller { public function index() { - $roles = Role::whereNotIn('name', ['Super Admin', 'admin', 'Designer']) + $roles = Role::whereIn('name', ['admin', 'Main Contractor Admin', 'Project Manager', 'Construction Supervisor', 'Site Technical']) ->with('permissions') ->get(); $permissions = Permission::all(); diff --git a/Modules/RolesPermissions/database/seeders/RolesPermissionsDatabaseSeeder.php b/Modules/RolesPermissions/database/seeders/RolesPermissionsDatabaseSeeder.php index 3e093ea..ba2ea36 100644 --- a/Modules/RolesPermissions/database/seeders/RolesPermissionsDatabaseSeeder.php +++ b/Modules/RolesPermissions/database/seeders/RolesPermissionsDatabaseSeeder.php @@ -35,25 +35,21 @@ class RolesPermissionsDatabaseSeeder extends Seeder // Clean up old roles that are no longer used $validRoles = [ 'Super Admin', - 'Contractor', 'Main Contractor Admin', - 'Main Contractor User', - 'Sub Contractor Admin', - 'Sub Contractor User', 'Project Manager', - 'Designer', 'Site Technical', - 'Construction Supervisor' + 'Construction Supervisor', + 'admin', ]; \Spatie\Permission\Models\Role::whereNotIn('name', $validRoles)->delete(); // Create Roles $superAdmin = \Spatie\Permission\Models\Role::firstOrCreate(['name' => 'Super Admin']); - $contractor = \Spatie\Permission\Models\Role::firstOrCreate(['name' => 'Contractor']); $projectManager = \Spatie\Permission\Models\Role::firstOrCreate(['name' => 'Project Manager']); - $designer = \Spatie\Permission\Models\Role::firstOrCreate(['name' => 'Designer']); $siteTechnical = \Spatie\Permission\Models\Role::firstOrCreate(['name' => 'Site Technical']); $constructionSupervisor = \Spatie\Permission\Models\Role::firstOrCreate(['name' => 'Construction Supervisor']); + $admin = \Spatie\Permission\Models\Role::firstOrCreate(['name' => 'admin']); + $admin->syncPermissions(\Spatie\Permission\Models\Permission::all()); $mainContractorAdmin = \Spatie\Permission\Models\Role::firstOrCreate(['name' => 'Main Contractor Admin']); $mainContractorAdmin->syncPermissions([ @@ -69,15 +65,6 @@ class RolesPermissionsDatabaseSeeder extends Seeder 'approvals.access', ]); - $subContractorAdmin = \Spatie\Permission\Models\Role::firstOrCreate(['name' => 'Sub Contractor Admin']); - $subContractorAdmin->syncPermissions([ - 'dashboard.access', - 'projects.access', - 'users.access', - 'finance.access', - 'documents.access', - ]); - $projectManager->syncPermissions([ 'dashboard.access', 'projects.access', @@ -95,21 +82,6 @@ class RolesPermissionsDatabaseSeeder extends Seeder 'delete tasks', ]); - $contractor->syncPermissions([ - 'dashboard.access', - 'projects.access', - 'bidding.access', - 'finance.access', - 'documents.access', - 'edit tasks', - ]); - - $designer->syncPermissions([ - 'dashboard.access', - 'projects.access', - 'documents.access', - ]); - $siteTechnical->syncPermissions([ 'dashboard.access', 'projects.access', diff --git a/Modules/TaskManagement/resources/js/Pages/Tasks/Index.tsx b/Modules/TaskManagement/resources/js/Pages/Tasks/Index.tsx index 7ca1e61..7e1cc4b 100644 --- a/Modules/TaskManagement/resources/js/Pages/Tasks/Index.tsx +++ b/Modules/TaskManagement/resources/js/Pages/Tasks/Index.tsx @@ -22,7 +22,7 @@ export default function Tasks({ project, employees, availableMaterials, delayRea 'Project Manager', 'Site Technical', 'Construction Supervisor', - 'Contractor', + 'Main Contractor Admin', ].some((role) => hasRole(role)); const [addTaskOpen, setAddTaskOpen] = useState(false); const [expandedTask, setExpandedTask] = useState(null); diff --git a/Modules/UserManagement/app/Http/Controllers/UserController.php b/Modules/UserManagement/app/Http/Controllers/UserController.php index ca5c8ea..5fee91d 100644 --- a/Modules/UserManagement/app/Http/Controllers/UserController.php +++ b/Modules/UserManagement/app/Http/Controllers/UserController.php @@ -66,7 +66,17 @@ class UserController extends Controller $nextNumber = $last ? ((int) substr($last, 4)) + 1 : 1; $nextCode = 'EMP-' . str_pad($nextNumber, 4, '0', STR_PAD_LEFT); - $roles = \Spatie\Permission\Models\Role::orderBy('name')->pluck('name'); + $roleOrder = [ + 'Super Admin', + 'admin', + 'Main Contractor Admin', + 'Project Manager', + 'Construction Supervisor', + 'Site Technical', + ]; + $roles = collect($roleOrder)->filter( + fn (string $role) => \Spatie\Permission\Models\Role::where('name', $role)->exists() + )->values(); return Inertia::render('UserManagement::Users/Create', [ 'isSuperAdmin' => $isSuperAdmin, @@ -108,7 +118,7 @@ class UserController extends Controller 'company_name' => ['nullable', 'string', 'max:255'], 'contact_person' => ['nullable', 'string', 'max:255'], 'tin_number' => ['nullable', 'string', 'max:255'], - 'spatie_role' => ['nullable', 'string', Rule::in(['Super Admin', 'admin', 'Contractor', 'Contractor Admin', 'Project Manager', 'Designer', 'Site Technical', 'Construction Supervisor'])], + 'spatie_role' => ['nullable', 'string', Rule::in(['Super Admin', 'admin', 'Main Contractor Admin', 'Project Manager', 'Site Technical', 'Construction Supervisor'])], ]); // Resolve contractor_id: contractor admins always use their own; @@ -133,13 +143,11 @@ class UserController extends Controller ]); // Determine Spatie role to assign - $roleToAssign = $validated['spatie_role'] ?? $validated['user_type']; - if ($roleToAssign === 'Contractor Admin') { - $roleToAssign = 'Contractor'; - } elseif ($roleToAssign === 'admin') { - $roleToAssign = 'Main Contractor Admin'; - } - + $roleToAssign = $validated['spatie_role'] ?? match ($validated['user_type']) { + 'admin' => 'admin', + 'contractor' => 'Main Contractor Admin', + default => 'Site Technical', + }; // Explicitly assign the spatie role matching the user type or selected role if ($roleToAssign) { $user->assignRole($roleToAssign); @@ -209,7 +217,7 @@ class UserController extends Controller 'company_name' => ['nullable', 'string', 'max:255'], 'contact_person'=> ['nullable', 'string', 'max:255'], 'tin_number' => ['nullable', 'string', 'max:255'], - 'spatie_role' => ['nullable', 'string', Rule::in(['Super Admin', 'Contractor', 'Project Manager', 'Designer', 'Site Technical', 'Construction Supervisor'])], + 'spatie_role' => ['nullable', 'string', Rule::in(['Super Admin', 'admin', 'Main Contractor Admin', 'Project Manager', 'Site Technical', 'Construction Supervisor'])], ]); $userData = [ @@ -225,10 +233,11 @@ class UserController extends Controller $user->update($userData); - $roleToAssign = $validated['spatie_role'] ?? $validated['user_type']; - if ($roleToAssign === 'admin') { - $roleToAssign = 'Super Admin'; - } + $roleToAssign = $validated['spatie_role'] ?? match ($validated['user_type']) { + 'admin' => 'admin', + 'contractor' => 'Main Contractor Admin', + default => 'Site Technical', + }; $user->syncRoles([$roleToAssign]); @@ -376,12 +385,12 @@ class UserController extends Controller continue; } - $allowedRoles = ['Super Admin', 'Contractor', 'Project Manager', 'Designer', 'Site Technical', 'Construction Supervisor']; - $resolvedRole = in_array($role, $allowedRoles) ? $role : 'Designer'; + $allowedRoles = ['Super Admin', 'admin', 'Main Contractor Admin', 'Project Manager', 'Site Technical', 'Construction Supervisor']; + $resolvedRole = in_array($role, $allowedRoles) ? $role : 'Site Technical'; // Map the Spatie role to the base user_type $userType = $resolvedRole; - if (in_array($resolvedRole, ['Contractor', 'Project Manager', 'Designer', 'Site Technical', 'Construction Supervisor'])) { + if (in_array($resolvedRole, ['Main Contractor Admin', 'Project Manager', 'Site Technical', 'Construction Supervisor'])) { $userType = 'contractor'; } diff --git a/Modules/UserManagement/app/Livewire/UserManager.php b/Modules/UserManagement/app/Livewire/UserManager.php index 4a76bfe..1a31f44 100644 --- a/Modules/UserManagement/app/Livewire/UserManager.php +++ b/Modules/UserManagement/app/Livewire/UserManager.php @@ -58,12 +58,8 @@ class UserManager extends Component $user->contractor->children()->pluck('id')->toArray() ); $this->users = User::whereIn('contractor_id', $contractorIds)->get(); - $this->availableRoles = ['Main Contractor Admin', 'Main Contractor User', 'Sub Contractor Admin', 'Sub Contractor User']; + $this->availableRoles = ['Main Contractor Admin', 'Project Manager', 'Construction Supervisor', 'Site Technical']; $this->availableContractors = Contractor::whereIn('id', $contractorIds)->get(); - } elseif ($user->hasRole('Sub Contractor Admin') && $user->contractor) { - $this->users = User::where('contractor_id', $user->contractor_id)->get(); - $this->availableRoles = ['Sub Contractor User']; - $this->availableContractors = collect([$user->contractor]); } else { $this->users = collect(); $this->availableRoles = []; diff --git a/Modules/UserManagement/resources/js/Pages/Users/Create.tsx b/Modules/UserManagement/resources/js/Pages/Users/Create.tsx index e6a1413..87ed91d 100644 --- a/Modules/UserManagement/resources/js/Pages/Users/Create.tsx +++ b/Modules/UserManagement/resources/js/Pages/Users/Create.tsx @@ -52,6 +52,11 @@ export default function Create({ contractors, isSuperAdmin, nextEmployeeCode, ro const [generatedPassword, setGeneratedPassword] = useState(''); + const roleLabels: Record = { + 'Main Contractor Admin': 'Contractor Admin', + admin: 'Admin', + }; + const generatePassword = () => { const chars = 'ABCDEFGHJKMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789!@#'; const pw = Array.from({ length: 10 }, () => chars[Math.floor(Math.random() * chars.length)]).join(''); @@ -231,8 +236,8 @@ export default function Create({ contractors, isSuperAdmin, nextEmployeeCode, ro - {(roles && roles.length > 0 ? roles : ['Super Admin', 'Admin', 'Project Manager', 'Contractor Admin', 'Construction Supervisor', 'Site Technical', 'Designer']).map((r) => ( - {r} + {(roles && roles.length > 0 ? roles : ['Super Admin', 'admin', 'Main Contractor Admin', 'Project Manager', 'Construction Supervisor', 'Site Technical']).map((r) => ( + {roleLabels[r] || r} ))} diff --git a/Modules/UserManagement/resources/js/Pages/Users/Edit.tsx b/Modules/UserManagement/resources/js/Pages/Users/Edit.tsx index eeaedb6..41d03f5 100644 --- a/Modules/UserManagement/resources/js/Pages/Users/Edit.tsx +++ b/Modules/UserManagement/resources/js/Pages/Users/Edit.tsx @@ -63,7 +63,7 @@ export default function Edit({ user, contractors, isPlatformAdmin }: Props) { const isEmployee = data.user_type === 'admin' || data.user_type === 'employee'; const isCustomer = data.user_type === 'customer'; - const isReadOnlyRole = data.spatie_role === 'Super Admin' || data.spatie_role === 'Contractor'; + const isReadOnlyRole = data.spatie_role === 'Super Admin'; return ( Project Manager - Designer + Contractor Admin + Admin Site Technical Construction Supervisor diff --git a/Modules/UserManagement/resources/views/livewire/user-manager.blade.php b/Modules/UserManagement/resources/views/livewire/user-manager.blade.php index fa2e431..2430670 100644 --- a/Modules/UserManagement/resources/views/livewire/user-manager.blade.php +++ b/Modules/UserManagement/resources/views/livewire/user-manager.blade.php @@ -39,7 +39,7 @@ @error('role') {{ $message }} @enderror diff --git a/app/Models/User.php b/app/Models/User.php index 8ceacf2..ba2eb7d 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -92,7 +92,7 @@ class User extends Authenticatable public function isContractorAdmin(): bool { return ! is_null($this->contractor_id) - && $this->hasRole('Contractor'); + && $this->hasRole('Main Contractor Admin'); } public function isContractorUser(): bool diff --git a/database/seeders/ConstructionRolesAndPermissionsSeeder.php b/database/seeders/ConstructionRolesAndPermissionsSeeder.php index 01dd589..005c336 100644 --- a/database/seeders/ConstructionRolesAndPermissionsSeeder.php +++ b/database/seeders/ConstructionRolesAndPermissionsSeeder.php @@ -55,14 +55,6 @@ class ConstructionRolesAndPermissionsSeeder extends Seeder // 2. Create Roles and assign permissions - // Role: Contractor (Main or Sub) - Usually sees their own items (handled by global scope), but has base permissions - $contractor = Role::firstOrCreate(['name' => 'Contractor']); - $contractor->givePermissionTo([ - 'edit tasks', - 'create daily reports', - 'upload documents', - ]); - // Role: Project Manager - Full access to project operations $pm = Role::firstOrCreate(['name' => 'Project Manager']); $pm->givePermissionTo([ @@ -77,12 +69,6 @@ class ConstructionRolesAndPermissionsSeeder extends Seeder 'edit master schedule', ]); - // Role: Designer - Focus on design documents and drawings - $designer = Role::firstOrCreate(['name' => 'Designer']); - $designer->givePermissionTo([ - 'upload documents', - ]); - // Role: Site Technical (Site Engineer) $siteTech = Role::firstOrCreate(['name' => 'Site Technical']); $siteTech->givePermissionTo([ diff --git a/database/seeders/RolesAndPermissionsSeeder.php b/database/seeders/RolesAndPermissionsSeeder.php index 32d6b2f..b5b6c59 100644 --- a/database/seeders/RolesAndPermissionsSeeder.php +++ b/database/seeders/RolesAndPermissionsSeeder.php @@ -35,7 +35,7 @@ class RolesAndPermissionsSeeder extends Seeder // Create roles and assign created permissions - // 1. Admin (formerly SuperAdmin) + // 1. Admin $admin = Role::firstOrCreate(['name' => 'admin']); $admin->givePermissionTo(Permission::all()); @@ -49,31 +49,5 @@ class RolesAndPermissionsSeeder extends Seeder 'view reports', ]); - // 3. Main Contractor User - $mainUser = Role::firstOrCreate(['name' => 'Main Contractor User']); - $mainUser->givePermissionTo([ - 'manage projects', - // scoped visibility is handled by scopes/policies, not just permission - ]); - - // 4. Sub Contractor Admin - $subAdmin = Role::firstOrCreate(['name' => 'Sub Contractor Admin']); - $subAdmin->givePermissionTo([ - 'manage users', - 'manage projects', - 'view reports', - ]); - - // 5. Sub Contractor User - $subUser = Role::firstOrCreate(['name' => 'Sub Contractor User']); - $subUser->givePermissionTo([ - 'manage projects', - ]); - - // 6. System Employee - $employee = Role::firstOrCreate(['name' => 'employee']); - - // 7. System Customer - $customer = Role::firstOrCreate(['name' => 'customer']); } } diff --git a/docs/WHOLE_WORKFLOW_SIMULATION_CHEAT_SHEET.txt b/docs/WHOLE_WORKFLOW_SIMULATION_CHEAT_SHEET.txt new file mode 100644 index 0000000..e78c031 --- /dev/null +++ b/docs/WHOLE_WORKFLOW_SIMULATION_CHEAT_SHEET.txt @@ -0,0 +1,348 @@ +GSB CONSTRUCTION ERP +WHOLE WORKFLOW SIMULATION CHEAT SHEET +Three contractor-isolated end-to-end test sets + +Purpose +------- +Run these three scenarios after the database seeders have been executed. The +materials, labor, and equipment names and rates below come from the seeded +catalog. Create the scenario users through User Management and link each user +to the contractor shown below. + +Seeded platform accounts +------------------------ +Super Administrator | superadmin@gsb-cons.com | password | Super Admin +Admin / Executive | admin@example.com | password | admin / Executive + +The two platform accounts are global. All contractor-linked accounts below +must only see their own contractor, direct subcontractors, project assignments, +and personnel-assigned projects. + +ROLE AND CONTRACTOR SETUP +========================= + +Use the contractors currently present in the database: +- Great SwissMetal Builders Corporation +- Apex Builders Corp +- Giga Ninja +- Test Contractor + +Create these users. Use password: password for every simulation account. + +SET 1 USERS: VERIDIAN TRADE CENTER +---------------------------------- +pm.veridian@example.com Project Manager Platform or Great SwissMetal +ca.veridian@example.com Main Contractor Admin Great SwissMetal Builders Corporation +sub1.veridian@example.com Sub Contractor Admin Apex Builders Corp +sub2.veridian@example.com Sub Contractor Admin Giga Ninja +tech.veridian@example.com Site Technical Great SwissMetal Builders Corporation +supervisor.veridian@example.com Construction Supervisor Great SwissMetal Builders Corporation +ops.veridian@example.com Site Operations Great SwissMetal Builders Corporation + +SET 2 USERS: RIVERSIDE RESIDENTIAL ESTATE +------------------------------------------ +pm.riverside@example.com Project Manager Platform or Giga Ninja +ca.riverside@example.com Main Contractor Admin Giga Ninja +sub1.riverside@example.com Sub Contractor Admin Test Contractor +sub2.riverside@example.com Sub Contractor Admin Apex Builders Corp +tech.riverside@example.com Site Technical Giga Ninja +supervisor.riverside@example.com Construction Supervisor Giga Ninja +ops.riverside@example.com Site Operations Giga Ninja + +SET 3 USERS: HARBOR VIEW BRIDGE EXPANSION +------------------------------------------ +pm.harbor@example.com Project Manager Platform or Test Contractor +ca.harbor@example.com Main Contractor Admin Test Contractor +sub1.harbor@example.com Sub Contractor Admin Great SwissMetal Builders Corporation +sub2.harbor@example.com Sub Contractor Admin Apex Builders Corp +tech.harbor@example.com Site Technical Test Contractor +supervisor.harbor@example.com Construction Supervisor Test Contractor +ops.harbor@example.com Site Operations Test Contractor + +For roles not present in a deployment, use the seeded equivalent shown in the +Roles and Permissions seeders: Main Contractor Admin, Sub Contractor Admin, +Project Manager, Site Technical, and Construction Supervisor. Use admin or +Super Admin as the Executive approver. + +COMMON WORKFLOW +============== + +Run the following stages for each set using that set's project and accounts. + +1. Contractor and user setup + - Confirm the contractor exists in Contractors. + - Create each user in Users. + - Assign the role and contractor link exactly as listed. + - Log in as a contractor user and confirm unrelated contractor projects do + not appear in Projects, dropdowns, modals, requisitions, purchase orders, + documents, reports, or finance forms. + +2. Project wizard (Project Manager) + - Create the project using the set-specific data below. + - Add the milestones and tasks. + - Add the seeded material, labor, and equipment allocations. + - Review the financial summary. + - Submit the project for approval and complete initialization. + - Expected result: project is active/initialized and visible to only the + related contractor users. + +3. Bidding package (Project Manager or Executive) + - Create a package for the initialized project. + - Invite the two subcontractors listed for the set. + - Publish the package. + - Expected result: the package is not left in Draft and is visible only to + invited contractors and authorized project-management users. + +4. Contractor bids + - Log in as each invited Sub Contractor Admin. + - Submit the proposed price, completion days, validity date, and notes. + - Confirm each contractor sees only its own bid and invitations plus the + projects related to its contractor. + +5. Bid evaluation and award + - Log in as Project Manager or admin/Super Admin. + - Compare both submissions. + - Award the selected submission below. + - Expected result: package becomes Awarded and the losing bid remains in + the comparison history. + +6. Cash advance + - Log in as Site Operations or Construction Supervisor. + - Create a mobilization cash advance for the project. + - Log in as Project Manager and admin/Super Admin. + - Approve the request from the approval center. + - Expected result: the request appears only to the relevant approvers and + contractor users for the related project. + +7. Materials requisition and purchase order + - Log in as Site Technical or Construction Supervisor. + - Create a requisition using one or more seeded materials. + - Submit it for approval. + - Approve it as Project Manager or admin/Super Admin. + - Create a purchase order from the approved requisition. + - Approve, mark paid, and mark delivered as authorized. + - Expected result: every project selector contains only related projects. + +8. Daily report and project execution + - Log in as Site Technical, Construction Supervisor, or Site Operations. + - Create a daily report using the set's labor and equipment. + - Include weather, work accomplished, manpower, and active equipment. + - Confirm dashboards show all applicable reports, not only the latest one. + +9. Invoice and retention + - Create a progress invoice for the awarded contractor. + - Submit it for approval. + - Approve as Project Manager and admin/Super Admin. + - Confirm the retention amount appears in the retention ledger. + - After completion, release the retention as Super Admin. + +10. Documents and final audit + - Upload one PDF, one DOCX, and one image under the project. + - Open each card and preview the media. + - Confirm project, title, and publisher are shown before opening the preview. + - Review approvals, finance totals, project history, and dashboard metrics. + +SET 1: VERIDIAN TRADE CENTER +============================ + +Project +------- +Name: Veridian Trade Center +Client: Veridian Holdings Inc. +Location: Ortigas Center, Pasig City +Contract value: PHP 15,000,000.00 +Dates: 2026-09-01 to 2027-09-01 + +Milestones and tasks +-------------------- +Phase 1: Deep Excavation & Piling (25%) + Site Excavation & Pile Driving | 2026-09-01 to 2026-10-31 +Phase 2: Structural Framing (Concrete & Steel) (40%) + Concrete Slab & Column Framing | 2026-11-01 to 2027-02-28 +Phase 3: Curtain Wall Solar Facade (20%) + Curtain Wall Bracket Anchorage | 2027-03-01 to 2027-05-31 +Phase 4: MEP & Architectural Interiors (15%) + Interior Fit-Out & Electrical MEP | 2027-06-01 to 2027-09-01 + +Seeded materials +---------------- +Portland Cement Type I | 5,000 Bag (40kg) | PHP 8.50 | PHP 42,500.00 +Ready Mix Concrete (30 MPa) | 1,200 Cubic Meter | PHP 130.00 | PHP 156,000.00 +Structural Steel Beam (W8x10) | 2,500 Foot | PHP 15.00 | PHP 37,500.00 +Material estimate total: PHP 236,000.00 + +Seeded labor +------------ +Journeyman Carpenter | 1,200 hours | PHP 95.00 | PHP 114,000.00 +Rebar Steelman | 1,500 hours | PHP 90.00 | PHP 135,000.00 +General Helper | 1,000 hours | PHP 65.00 | PHP 65,000.00 +Labor estimate total: PHP 314,000.00 + +Seeded equipment +---------------- +Caterpillar 320 Excavator | 300 hours | PHP 1,500.00 | PHP 450,000.00 +Concrete Mixer (One-bagger) | 400 hours | PHP 350.00 | PHP 140,000.00 +Equipment estimate total: PHP 590,000.00 +Combined estimate: PHP 1,140,000.00 + +Bidding +------- +Package: Facade Structural Framing & Solar Glazing +Invite: Apex Builders Corp and Giga Ninja +Bid A, Apex Builders Corp: PHP 4,200,000.00 | 75 days +Bid B, Giga Ninja: PHP 3,850,000.00 | 90 days +Award: Apex Builders Corp for premium facade quality and shorter duration. + +Finance and execution +--------------------- +Cash advance: PHP 250,000.00 for mobilization and safety anchors. +Daily report: 12 Glass Installers, 6 Structural Welders, 1 active tower crane. +Progress invoice: PHP 1,260,000.00 gross, 10% retention PHP 126,000.00, +net payment PHP 1,134,000.00. + +SET 2: RIVERSIDE RESIDENTIAL ESTATE +=================================== + +Project +------- +Name: Riverside Residential Estate +Client: Riverside Development Group +Location: Nuvali, Laguna +Contract value: PHP 9,500,000.00 +Dates: 2026-10-01 to 2027-06-30 + +Milestones and tasks +-------------------- +Site Development (20%) + Site Clearing and Preparation | 2026-10-01 to 2026-10-31 +Structural and Masonry Works (35%) + Foundation Concrete Pouring | 2026-11-01 to 2027-01-31 +MEP Rough-In (25%) + Electrical Wiring Installation | 2027-02-01 to 2027-04-30 +Architectural Finishing (20%) + Interior Drywall Installation | 2027-05-01 to 2027-06-30 + +Seeded materials +---------------- +Drywall (Gypsum Board) 1/2 (4x8 ft) | 600 Sheet | PHP 13.00 | PHP 7,800.00 +2x4 Framing Lumber (8 ft) | 800 Piece | PHP 3.50 | PHP 2,800.00 +NM-B Wire 12/2 (250 ft Roll) | 10 Roll | PHP 85.00 | PHP 850.00 +PVC Pipe SCH 40 (2 x 10 ft) | 100 Piece | PHP 15.00 | PHP 1,500.00 +Interior Flat Paint (White) 5 Gal | 30 Bucket | PHP 70.00 | PHP 2,100.00 +Material estimate total: PHP 15,050.00 + +Seeded labor +------------ +Journeyman Carpenter | 800 hours | PHP 95.00 | PHP 76,000.00 +Senior Electrician | 500 hours | PHP 105.00 | PHP 52,500.00 +Skilled Mason | 600 hours | PHP 90.00 | PHP 54,000.00 +Labor estimate total: PHP 182,500.00 + +Seeded equipment +---------------- +Concrete Mixer (One-bagger) | 200 hours | PHP 350.00 | PHP 70,000.00 +Generac 50kW Generator | 100 hours | PHP 800.00 | PHP 80,000.00 +Stihl Concrete Saw | 120 hours | PHP 200.00 | PHP 24,000.00 +Equipment estimate total: PHP 174,000.00 +Combined estimate: PHP 371,550.00 + +Bidding +------- +Package: Residential MEP and Interior Fit-out +Invite: Test Contractor and Apex Builders Corp +Bid A, Test Contractor: PHP 2,400,000.00 | 120 days +Bid B, Apex Builders Corp: PHP 2,550,000.00 | 105 days +Award: Apex Builders Corp for the shorter completion schedule. + +Finance and execution +--------------------- +Cash advance: PHP 180,000.00 for site setup and temporary power. +Daily report: 8 Carpenters, 4 Electricians, 3 Masons, 1 active generator. +Progress invoice: PHP 765,000.00 gross, 10% retention PHP 76,500.00, +net payment PHP 688,500.00. + +SET 3: HARBOR VIEW BRIDGE EXPANSION +=================================== + +Project +------- +Name: Harbor View Bridge Expansion +Client: Harbor Infrastructure Authority +Location: Cebu Coastal Corridor +Contract value: PHP 22,000,000.00 +Dates: 2026-11-01 to 2027-10-31 + +Milestones and tasks +-------------------- +Temporary Works and Excavation (25%) + Excavation and Trenching | 2026-11-01 to 2027-01-31 +Reinforced Concrete Works (40%) + Foundation Concrete Pouring | 2027-02-01 to 2027-05-31 +Steel Works (25%) + Steel Framework Erection | 2027-06-01 to 2027-08-31 +Finishing and Safety Inspection (10%) + Final Inspection and Punch List | 2027-09-01 to 2027-10-31 + +Seeded materials +---------------- +Rebar #4 (1/2 x 20 ft) | 3,000 Piece | PHP 6.20 | PHP 18,600.00 +River Sand | 400 Ton | PHP 20.00 | PHP 8,000.00 +Crushed Stone (Gravel) 3/4 | 500 Ton | PHP 25.00 | PHP 12,500.00 +Ready Mix Concrete (30 MPa) | 800 Cubic Meter | PHP 130.00 | PHP 104,000.00 +Tie Wire (Black Annealed) 16 Gauge | 100 Roll (3 lb) | PHP 8.00 | PHP 800.00 +Material estimate total: PHP 143,900.00 + +Seeded labor +------------ +Construction Foreman | 800 hours | PHP 150.00 | PHP 120,000.00 +Lead Welder | 1,000 hours | PHP 110.00 | PHP 110,000.00 +Rebar Steelman | 1,200 hours | PHP 90.00 | PHP 108,000.00 +General Helper | 1,500 hours | PHP 65.00 | PHP 97,500.00 +Labor estimate total: PHP 435,500.00 + +Seeded equipment +---------------- +Caterpillar 320 Excavator | 250 hours | PHP 1,500.00 | PHP 375,000.00 +10-Wheeler Dump Truck | 200 hours | PHP 1,200.00 | PHP 240,000.00 +Hilti TE-3000 Jackhammer | 80 hours | PHP 150.00 | PHP 12,000.00 +Equipment estimate total: PHP 627,000.00 +Combined estimate: PHP 1,206,400.00 + +Bidding +------- +Package: Bridge Foundation and Structural Steel Works +Invite: Great SwissMetal Builders Corporation and Apex Builders Corp +Bid A, Great SwissMetal Builders Corporation: PHP 6,800,000.00 | 210 days +Bid B, Apex Builders Corp: PHP 6,450,000.00 | 225 days +Award: Great SwissMetal Builders Corporation for stronger structural-work capacity. + +Finance and execution +--------------------- +Cash advance: PHP 400,000.00 for heavy equipment mobilization. +Daily report: 4 Foremen, 10 Welders, 12 Rebar Steelmen, 20 Helpers, +1 excavator and 1 dump truck active. +Progress invoice: PHP 2,040,000.00 gross, 10% retention PHP 204,000.00, +net payment PHP 1,836,000.00. + +FINAL VERIFICATION CHECKLIST +============================ + +[ ] Each scenario has a distinct project and contractor relationship. +[ ] Contractor Admin sees only its own contractor-related projects. +[ ] Sub Contractor Admin sees only invited/related project data. +[ ] Site Technical, Construction Supervisor, and Site Operations see only + contractor-related or personnel-assigned projects. +[ ] Project Manager can create and manage bidding packages. +[ ] Executive/admin can approve cash advances, requisitions, purchase orders, + invoices, and retention. +[ ] A bid package changes from Draft to Open/Published before contractors can + submit bids. +[ ] The losing bid remains visible in evaluation history. +[ ] Project dropdowns in pages, modals, requisitions, purchase orders, + documents, reports, and finance forms exclude unrelated projects. +[ ] Material, labor, and equipment totals match the line-item calculations. +[ ] Daily dashboards aggregate all relevant reports, not only the latest. +[ ] PDF, DOCX, and image document previews open after selecting a card. +[ ] Retention is withheld, displayed in the ledger, and released after project + completion. +[ ] Super Admin can audit all three sets; contractor users cannot cross tenants.