diff --git a/.agents/CHANGELOG.md b/.agents/CHANGELOG.md deleted file mode 100644 index c82ee4b..0000000 --- a/.agents/CHANGELOG.md +++ /dev/null @@ -1,74 +0,0 @@ -# AG Kit Toolkit Changelog - -## Unreleased - -### Changed - -- Updated the `mcp-builder` skill for the stable MCP `2026-07-28` specification: stateless per-request metadata, `server/discover`, explicit state handles, extension negotiation, JSON Schema 2020-12, compatibility behavior, and migration guidance for deprecated features. -- Clearly separated stable core features from opt-in Tasks, Skills over MCP, and MCP Apps extensions. -- Reworked the orchestrator and `parallel-agents` guidance around Antigravity-native agents and tasks while retaining best-effort portability for other runtimes. -- Removed Claude-specific built-in agent and model-tier assumptions from managed orchestration instructions; runtime capabilities must now be discovered before delegation. - -### Security - -- Added required safeguards for external `$ref` resolution, schema-validation resource limits, untrusted tool annotations, explicit consent, least privilege, secret handling, and execution isolation. -- Added explicit trust boundaries for repository content, MCP responses, tool annotations, web content, logs, and subagent outputs. -- Added finite agent, delegation-depth, turn/retry, timeout, cancellation, and no-progress controls to prevent recursive delegation and indefinite ReAct loops. -- Parallel writers now require isolated worktrees, sandboxes, branches, or non-overlapping path grants, followed by coordinator-owned integration and repository-wide verification. - -## 2026.7.26 - -### Added - -- Antigravity runtime contract with six production integration phases. -- Native `PreToolUse` hook and destructive-command policy. -- Antigravity Doctor, MCP synchronization helper, plugin builder, schemas, and regression tests. -- Complete migration, production checklist, security, and operator documentation. - -### Changed - -- Toolkit version advanced from `2026.7.18` to `2026.7.26`. -- Google Antigravity is the primary production runtime; other Markdown-compatible tools are best-effort consumers. -- Component manifest now records Antigravity runtime metadata. -- Integrity lock now covers `antigravity.json`, `hooks.json`, and the complete `hooks/` runtime-tooling tree. -- Self-validation and Antigravity Doctor enforce synchronized root, CLI, web, and toolkit versions. - -### Security - -- High-confidence root/disk destructive commands are blocked before tool execution. -- Invalid or unknown hook payloads fail open with a warning to avoid runtime-wide lockout. -- MCP writes remain explicit, placeholder-blocked, conflict-aware, and backup-protected. -- Plugin artifacts are reviewable and contain no home-directory configuration or environment secrets. - -### Compatibility - -- Existing agent, skill, workflow, rule, and memory names remain compatible. -- The native safety hook is enabled by default and can be temporarily disabled for compatibility diagnosis. -- Plugin installation is optional; repository `.agents/` remains the project source of truth. - -## 2026.7.18 - -### Added - -- Strict SemVer metadata for all 20 agents, 47 skills, 13 workflows, and 6 rules. -- Machine-readable `manifest.json` with agent-to-skill and workflow dependencies. -- Deterministic `manifest.lock.json` with SHA-256 integrity hashes. -- Generated `DEPENDENCY_GRAPH.md` for workflow → agent → skill orchestration. -- JSON schemas for component metadata, manifest, lock, and memory topics. -- Standard memory topic files for user preferences, technical decisions, and feedback history. -- Registry and graph generation scripts with non-mutating `--check` modes. - -### Changed - -- Toolkit version advanced from `2026.7.12` to `2026.7.18`. -- Self-validation now checks component versions, workflow references, dependency compatibility, registry drift, lock integrity, graph drift, and memory contracts. -- CI now treats generated registry files as release artifacts that must remain synchronized. - -### Compatibility - -- Official runtime support remained Gemini CLI and Google Antigravity for that release. -- The component metadata and dependency format remain portable and avoid unnecessary platform coupling. - -## 2026.7.12 - -- Release-safety upgrade, non-destructive CLI updates, rollback support, CI, dependency review, and hardened publishing. diff --git a/.github/workflows/issues.yml b/.github/workflows/issues.yml deleted file mode 100644 index 230257c..0000000 --- a/.github/workflows/issues.yml +++ /dev/null @@ -1,12 +0,0 @@ -name: Issues - -on: - issues: - types: [labeled] - -permissions: - issues: write - -jobs: - help-wanted: - uses: laravel/.github/.github/workflows/issues.yml@main diff --git a/.github/workflows/pull-requests.yml b/.github/workflows/pull-requests.yml deleted file mode 100644 index 6f9f97e..0000000 --- a/.github/workflows/pull-requests.yml +++ /dev/null @@ -1,12 +0,0 @@ -name: Pull Requests - -on: - pull_request_target: - types: [opened] - -permissions: - pull-requests: write - -jobs: - uneditable: - uses: laravel/.github/.github/workflows/pull-requests.yml@main diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml deleted file mode 100644 index 52fa62c..0000000 --- a/.github/workflows/tests.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: Tests - -on: - push: - branches: - - master - - '*.x' - pull_request: - schedule: - - cron: '0 0 * * *' - -permissions: - contents: read - -jobs: - tests: - runs-on: ubuntu-latest - - strategy: - fail-fast: true - matrix: - php: [8.3, 8.4, 8.5] - - name: PHP ${{ matrix.php }} - - steps: - - name: Checkout code - uses: actions/checkout@v6 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php }} - extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite - coverage: none - - - name: Install Composer dependencies - run: composer install --prefer-dist --no-interaction --no-progress - - - name: Copy environment file - run: cp .env.example .env - - - name: Generate app key - run: php artisan key:generate - - - name: Execute tests - run: php artisan test diff --git a/.github/workflows/update-changelog.yml b/.github/workflows/update-changelog.yml deleted file mode 100644 index 7035233..0000000 --- a/.github/workflows/update-changelog.yml +++ /dev/null @@ -1,13 +0,0 @@ -name: Update Changelog - -on: - release: - types: [released] - -permissions: {} - -jobs: - update: - permissions: - contents: write - uses: laravel/.github/.github/workflows/update-changelog.yml@main diff --git a/.styleci.yml b/.styleci.yml deleted file mode 100644 index 9daadf1..0000000 --- a/.styleci.yml +++ /dev/null @@ -1,9 +0,0 @@ -php: - preset: laravel - disabled: - - no_unused_imports - finder: - not-name: - - index.php -js: true -css: true diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index a4a4f98..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,163 +0,0 @@ -# Release Notes - -## [Unreleased](https://github.com/laravel/laravel/compare/v12.12.2...12.x) - -## [v12.12.2](https://github.com/laravel/laravel/compare/v12.12.1...v12.12.2) - 2026-03-14 - -* [12.x] Add `APP_NAME` fallback in Slack log channel username by [@hamedelasma](https://github.com/hamedelasma) in https://github.com/laravel/laravel/pull/6762 - -## [v12.12.1](https://github.com/laravel/laravel/compare/v12.12.0...v12.12.1) - 2026-03-10 - -* [12.x] Makes imports consistent by [@nunomaduro](https://github.com/nunomaduro) in https://github.com/laravel/laravel/pull/6760 - -## [v12.12.0](https://github.com/laravel/laravel/compare/v12.11.2...v12.12.0) - 2026-03-09 - -* Update phpunit version to ^11.5.50 to address CVE by [@PerryvanderMeer](https://github.com/PerryvanderMeer) in https://github.com/laravel/laravel/pull/6746 -* [12.x] Add `APP_NAME` fallback in mail config by [@apoorvdarshan](https://github.com/apoorvdarshan) in https://github.com/laravel/laravel/pull/6755 -* [12.x] Neutralize DB_URL in default phpunit.xml by [@Husseinadq](https://github.com/Husseinadq) in https://github.com/laravel/laravel/pull/6761 - -## [v12.11.2](https://github.com/laravel/laravel/compare/v12.11.1...v12.11.2) - 2026-01-19 - -* [12.x] Update composer dev script to ensure no timeout by [@jackbayliss](https://github.com/jackbayliss) in https://github.com/laravel/laravel/pull/6735 -* [12.x] Update jobs/cache migrations by [@jackbayliss](https://github.com/jackbayliss) in https://github.com/laravel/laravel/pull/6736 -* [12.x] Remove failed jobs indexes by [@jackbayliss](https://github.com/jackbayliss) in https://github.com/laravel/laravel/pull/6739 -* [12.x] Add `APP_URL` fallback in filesystems config by [@KentarouTakeda](https://github.com/KentarouTakeda) in https://github.com/laravel/laravel/pull/6742 -* chore: Update outdated GitHub Actions version by [@pgoslatara](https://github.com/pgoslatara) in https://github.com/laravel/laravel/pull/6743 - -## [v12.11.1](https://github.com/laravel/laravel/compare/v12.11.0...v12.11.1) - 2025-12-23 - -* Use environment variable for `DB_SSLMODE` - Postgres by [@robsontenorio](https://github.com/robsontenorio) in https://github.com/laravel/laravel/pull/6727 -* fix: ensure APP_URL does not have trailing slash in filesystem by [@msamgan](https://github.com/msamgan) in https://github.com/laravel/laravel/pull/6728 - -## [v12.11.0](https://github.com/laravel/laravel/compare/v12.10.1...v12.11.0) - 2025-11-25 - -* fix: cookies are not available for subdomains by default by [@joostdebruijn](https://github.com/joostdebruijn) in https://github.com/laravel/laravel/pull/6705 -* Fix PHP 8.5 PDO Driver Specific Constant Deprecation by [@RyanSchaefer](https://github.com/RyanSchaefer) in https://github.com/laravel/laravel/pull/6710 -* Ignore Laravel compiled views for Vite by [@QistiAmal1212](https://github.com/QistiAmal1212) in https://github.com/laravel/laravel/pull/6714 - -## [v12.10.1](https://github.com/laravel/laravel/compare/v12.10.0...v12.10.1) - 2025-11-06 - -* Update schema URL in package.json by [@robinmiau](https://github.com/robinmiau) in https://github.com/laravel/laravel/pull/6701 - -## [v12.10.0](https://github.com/laravel/laravel/compare/v12.9.1...v12.10.0) - 2025-11-04 - -* Add background driver by [@barryvdh](https://github.com/barryvdh) in https://github.com/laravel/laravel/pull/6699 - -## [v12.9.1](https://github.com/laravel/laravel/compare/v12.9.0...v12.9.1) - 2025-10-23 - -* [12.x] Replace Bootcamp with Laravel Learn by [@AhmedAlaa4611](https://github.com/AhmedAlaa4611) in https://github.com/laravel/laravel/pull/6692 -* [12.x] Comment out CLI workers for fresh applications by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/laravel/pull/6693 - -## [v12.9.0](https://github.com/laravel/laravel/compare/v12.8.0...v12.9.0) - 2025-10-21 - -**Full Changelog**: https://github.com/laravel/laravel/compare/v12.8.0...v12.9.0 - -## [v12.8.0](https://github.com/laravel/laravel/compare/v12.7.1...v12.8.0) - 2025-10-20 - -* [12.x] Makes test suite using broadcast's `null` driver by [@nunomaduro](https://github.com/nunomaduro) in https://github.com/laravel/laravel/pull/6691 - -## [v12.7.1](https://github.com/laravel/laravel/compare/v12.7.0...v12.7.1) - 2025-10-15 - -* Added `failover` driver to the `queue` config comment. by [@sajjadhossainshohag](https://github.com/sajjadhossainshohag) in https://github.com/laravel/laravel/pull/6688 - -## [v12.7.0](https://github.com/laravel/laravel/compare/v12.6.0...v12.7.0) - 2025-10-14 - -**Full Changelog**: https://github.com/laravel/laravel/compare/v12.6.0...v12.7.0 - -## [v12.6.0](https://github.com/laravel/laravel/compare/v12.5.0...v12.6.0) - 2025-10-02 - -* Fix setup script by [@goldmont](https://github.com/goldmont) in https://github.com/laravel/laravel/pull/6682 - -## [v12.5.0](https://github.com/laravel/laravel/compare/v12.4.0...v12.5.0) - 2025-09-30 - -* [12.x] Fix type casting for environment variables in config files by [@AhmedAlaa4611](https://github.com/AhmedAlaa4611) in https://github.com/laravel/laravel/pull/6670 -* Fix CVEs affecting vite by [@faissaloux](https://github.com/faissaloux) in https://github.com/laravel/laravel/pull/6672 -* Update .editorconfig to target compose.yaml by [@fredikaputra](https://github.com/fredikaputra) in https://github.com/laravel/laravel/pull/6679 -* Add pre-package-uninstall script to composer.json by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/laravel/pull/6681 - -## [v12.4.0](https://github.com/laravel/laravel/compare/v12.3.1...v12.4.0) - 2025-08-29 - -* [12.x] Add default Redis retry configuration by [@mateusjatenee](https://github.com/mateusjatenee) in https://github.com/laravel/laravel/pull/6666 - -## [v12.3.1](https://github.com/laravel/laravel/compare/v12.3.0...v12.3.1) - 2025-08-21 - -* [12.x] Bump Pint version by [@AhmedAlaa4611](https://github.com/AhmedAlaa4611) in https://github.com/laravel/laravel/pull/6653 -* [12.x] Making sure all related processed are closed when terminating the currently command by [@AhmedAlaa4611](https://github.com/AhmedAlaa4611) in https://github.com/laravel/laravel/pull/6654 -* [12.x] Use application name from configuration by [@AhmedAlaa4611](https://github.com/AhmedAlaa4611) in https://github.com/laravel/laravel/pull/6655 -* Bring back postAutoloadDump script by [@jasonvarga](https://github.com/jasonvarga) in https://github.com/laravel/laravel/pull/6662 - -## [v12.3.0](https://github.com/laravel/laravel/compare/v12.2.0...v12.3.0) - 2025-08-03 - -* Fix Critical Security Vulnerability in form-data Dependency by [@izzygld](https://github.com/izzygld) in https://github.com/laravel/laravel/pull/6645 -* Revert "fix" by [@RobertBoes](https://github.com/RobertBoes) in https://github.com/laravel/laravel/pull/6646 -* Change composer post-autoload-dump script to Artisan command by [@lmjhs](https://github.com/lmjhs) in https://github.com/laravel/laravel/pull/6647 - -## [v12.2.0](https://github.com/laravel/laravel/compare/v12.1.0...v12.2.0) - 2025-07-11 - -* Add Vite 7 support by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/laravel/pull/6639 - -## [v12.1.0](https://github.com/laravel/laravel/compare/v12.0.11...v12.1.0) - 2025-07-03 - -* [12.x] Disable nightwatch in testing by [@laserhybiz](https://github.com/laserhybiz) in https://github.com/laravel/laravel/pull/6632 -* [12.x] Reorder environment variables in phpunit.xml for logical grouping by [@AhmedAlaa4611](https://github.com/AhmedAlaa4611) in https://github.com/laravel/laravel/pull/6634 -* Change to hyphenate prefixes and cookie names by [@u01jmg3](https://github.com/u01jmg3) in https://github.com/laravel/laravel/pull/6636 -* [12.x] Fix type casting for environment variables in config files by [@AhmedAlaa4611](https://github.com/AhmedAlaa4611) in https://github.com/laravel/laravel/pull/6637 - -## [v12.0.11](https://github.com/laravel/laravel/compare/v12.0.10...v12.0.11) - 2025-06-10 - -**Full Changelog**: https://github.com/laravel/laravel/compare/v12.0.10...v12.0.11 - -## [v12.0.10](https://github.com/laravel/laravel/compare/v12.0.9...v12.0.10) - 2025-06-09 - -* fix alphabetical order by [@Khuthaily](https://github.com/Khuthaily) in https://github.com/laravel/laravel/pull/6627 -* [12.x] Reduce redundancy and keeps the .gitignore file cleaner by [@AhmedAlaa4611](https://github.com/AhmedAlaa4611) in https://github.com/laravel/laravel/pull/6629 -* [12.x] Fix: Add void return type to satisfy Rector analysis by [@Aluisio-Pires](https://github.com/Aluisio-Pires) in https://github.com/laravel/laravel/pull/6628 - -## [v12.0.9](https://github.com/laravel/laravel/compare/v12.0.8...v12.0.9) - 2025-05-26 - -* [12.x] Remove apc by [@AhmedAlaa4611](https://github.com/AhmedAlaa4611) in https://github.com/laravel/laravel/pull/6611 -* [12.x] Add JSON Schema to package.json by [@martinbean](https://github.com/martinbean) in https://github.com/laravel/laravel/pull/6613 -* Minor language update by [@woganmay](https://github.com/woganmay) in https://github.com/laravel/laravel/pull/6615 -* Enhance .gitignore to exclude common OS and log files by [@mohammadRezaei1380](https://github.com/mohammadRezaei1380) in https://github.com/laravel/laravel/pull/6619 - -## [v12.0.8](https://github.com/laravel/laravel/compare/v12.0.7...v12.0.8) - 2025-05-12 - -* [12.x] Clean up URL formatting in README by [@AhmedAlaa4611](https://github.com/AhmedAlaa4611) in https://github.com/laravel/laravel/pull/6601 - -## [v12.0.7](https://github.com/laravel/laravel/compare/v12.0.6...v12.0.7) - 2025-04-15 - -* Add `composer run test` command by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/laravel/pull/6598 -* Partner Directory Changes in ReadME by [@joshcirre](https://github.com/joshcirre) in https://github.com/laravel/laravel/pull/6599 - -## [v12.0.6](https://github.com/laravel/laravel/compare/v12.0.5...v12.0.6) - 2025-04-08 - -**Full Changelog**: https://github.com/laravel/laravel/compare/v12.0.5...v12.0.6 - -## [v12.0.5](https://github.com/laravel/laravel/compare/v12.0.4...v12.0.5) - 2025-04-02 - -* [12.x] Update `config/mail.php` to match the latest core configuration by [@AhmedAlaa4611](https://github.com/AhmedAlaa4611) in https://github.com/laravel/laravel/pull/6594 - -## [v12.0.4](https://github.com/laravel/laravel/compare/v12.0.3...v12.0.4) - 2025-03-31 - -* Bump vite from 6.0.11 to 6.2.3 - Vulnerability patch by [@abdel-aouby](https://github.com/abdel-aouby) in https://github.com/laravel/laravel/pull/6586 -* Bump vite from 6.2.3 to 6.2.4 by [@thinkverse](https://github.com/thinkverse) in https://github.com/laravel/laravel/pull/6590 - -## [v12.0.3](https://github.com/laravel/laravel/compare/v12.0.2...v12.0.3) - 2025-03-17 - -* Remove reverted change from CHANGELOG.md by [@AJenbo](https://github.com/AJenbo) in https://github.com/laravel/laravel/pull/6565 -* Improves clarity in app.css file by [@AhmedAlaa4611](https://github.com/AhmedAlaa4611) in https://github.com/laravel/laravel/pull/6569 -* [12.x] Refactor: Structural improvement for clarity by [@AhmedAlaa4611](https://github.com/AhmedAlaa4611) in https://github.com/laravel/laravel/pull/6574 -* Bump axios from 1.7.9 to 1.8.2 - Vulnerability patch by [@abdel-aouby](https://github.com/abdel-aouby) in https://github.com/laravel/laravel/pull/6572 -* [12.x] Remove Unnecessarily [@source](https://github.com/source) by [@AhmedAlaa4611](https://github.com/AhmedAlaa4611) in https://github.com/laravel/laravel/pull/6584 - -## [v12.0.2](https://github.com/laravel/laravel/compare/v12.0.1...v12.0.2) - 2025-03-04 - -* Make the github test action run out of the box independent of the choice of testing framework by [@ndeblauw](https://github.com/ndeblauw) in https://github.com/laravel/laravel/pull/6555 - -## [v12.0.1](https://github.com/laravel/laravel/compare/v12.0.0...v12.0.1) - 2025-02-24 - -* [12.x] prefer stable stability by [@pataar](https://github.com/pataar) in https://github.com/laravel/laravel/pull/6548 - -## [v12.0.0 (2025-??-??)](https://github.com/laravel/laravel/compare/v11.0.2...v12.0.0) - -Laravel 12 includes a variety of changes to the application skeleton. Please consult the diff to see what's new. diff --git a/DailyReport_PRJ-2026-001_20260825.pdf b/DailyReport_PRJ-2026-001_20260825.pdf new file mode 100644 index 0000000..7c47ecb Binary files /dev/null and b/DailyReport_PRJ-2026-001_20260825.pdf differ diff --git a/DailyReport_PRJ-2026-001_20260825.xlsx b/DailyReport_PRJ-2026-001_20260825.xlsx new file mode 100644 index 0000000..1dd52bb Binary files /dev/null and b/DailyReport_PRJ-2026-001_20260825.xlsx differ diff --git a/GreatswissERPsignoff (1).docx b/GreatswissERPsignoff (1).docx new file mode 100644 index 0000000..4f22640 Binary files /dev/null and b/GreatswissERPsignoff (1).docx differ diff --git a/GreatswissERPsignoff (2).docx b/GreatswissERPsignoff (2).docx new file mode 100644 index 0000000..4924de5 Binary files /dev/null and b/GreatswissERPsignoff (2).docx differ diff --git a/GreatswissERPsignoff.docx b/GreatswissERPsignoff.docx new file mode 100644 index 0000000..4924de5 Binary files /dev/null and b/GreatswissERPsignoff.docx differ diff --git a/Modules/ApprovalWorkflow/app/Http/Controllers/ApprovalController.php b/Modules/ApprovalWorkflow/app/Http/Controllers/ApprovalController.php index 3ad59f2..9069821 100644 --- a/Modules/ApprovalWorkflow/app/Http/Controllers/ApprovalController.php +++ b/Modules/ApprovalWorkflow/app/Http/Controllers/ApprovalController.php @@ -27,23 +27,23 @@ class ApprovalController extends Controller $isAdmin = $user->user_type === 'admin' || $user->hasRole(['Super Admin', 'admin', 'Main Contractor Admin']); $isApprovingRole = $isAdmin || $user->hasRole(['Project Manager', 'Main Contractor Admin']); - $isContractorSide = $user->user_type === 'contractor' || $user->hasRole(['Contractor Admin', 'Contractor', 'Subcontractor Staff', 'Site Technical', 'Construction Supervisor']); + $isContractorSide = $user->user_type === 'contractor' || $user->hasRole(['Contractor Admin', 'Contractor', 'Contractor Project Manager', 'Subcontractor Staff', 'Site Technical', 'Construction Supervisor']); $query = ApprovalChain::query(); // Project Managers are not authorized to review/approve project estimation requests. if ($user->hasRole('Project Manager') && !$isAdmin) { $query->where(function ($q) { - $q->whereNull('chain_type') - ->orWhere('chain_type', '!=', 'project_estimation'); + $q->whereNull('type') + ->orWhere('type', '!=', 'project_estimation'); })->where('approvable_type', '!=', Project::class); } // Contractor side users are not authorized to review/approve Material Requisitions. if ($isContractorSide && !$isAdmin) { $query->where(function ($q) { - $q->whereNull('chain_type') - ->orWhere('chain_type', '!=', 'material_requisition'); + $q->whereNull('type') + ->orWhere('type', '!=', 'material_requisition'); })->where('approvable_type', '!=', \Modules\MaterialLogistics\Models\MaterialRequisition::class); } @@ -106,13 +106,13 @@ class ApprovalController extends Controller { $user = request()->user(); $isAdmin = $user->user_type === 'admin' || $user->hasRole(['Super Admin', 'admin', 'Main Contractor Admin']); - $isContractorSide = $user->user_type === 'contractor' || $user->hasRole(['Contractor Admin', 'Contractor', 'Subcontractor Staff', 'Site Technical', 'Construction Supervisor']); + $isContractorSide = $user->user_type === 'contractor' || $user->hasRole(['Contractor Admin', 'Contractor', 'Contractor Project Manager', 'Subcontractor Staff', 'Site Technical', 'Construction Supervisor']); - if (($approvalChain->chain_type === 'project_estimation' || $approvalChain->approvable_type === Project::class) && $user->hasRole('Project Manager') && !$isAdmin) { + if (($approvalChain->type === 'project_estimation' || $approvalChain->approvable_type === Project::class) && $user->hasRole('Project Manager') && !$isAdmin) { return redirect()->route('approvals.index')->with('error', 'Project Managers are not authorized to view or approve project estimation reviews.'); } - if (($approvalChain->chain_type === 'material_requisition' || $approvalChain->approvable_type === \Modules\MaterialLogistics\Models\MaterialRequisition::class) && $isContractorSide && !$isAdmin) { + if (($approvalChain->type === 'material_requisition' || $approvalChain->approvable_type === \Modules\MaterialLogistics\Models\MaterialRequisition::class) && $isContractorSide && !$isAdmin) { return redirect()->route('approvals.index')->with('error', 'Contractor side roles are not authorized to view or approve Material Requisitions.'); } diff --git a/Modules/BiddingManagement/app/Enums/BidPackageStatus.php b/Modules/BiddingManagement/app/Enums/BidPackageStatus.php deleted file mode 100644 index b60d9da..0000000 --- a/Modules/BiddingManagement/app/Enums/BidPackageStatus.php +++ /dev/null @@ -1,45 +0,0 @@ - 'Draft', - self::Open => 'Open', - self::Evaluating => 'Evaluating', - self::Awarded => 'Awarded', - self::Cancelled => 'Cancelled', - }; - } - - public function allowedTransitions(): array - { - return match ($this) { - self::Draft => [self::Open, self::Cancelled], - self::Open => [self::Evaluating, self::Awarded, self::Cancelled], - self::Evaluating => [self::Awarded, self::Cancelled], - self::Awarded => [], - self::Cancelled => [], - }; - } - - public function color(): string - { - return match ($this) { - self::Draft => 'secondary', - self::Open => 'default', - self::Evaluating => 'warning', - self::Awarded => 'success', - self::Cancelled => 'destructive', - }; - } -} diff --git a/Modules/BiddingManagement/app/Enums/BidSubmissionStatus.php b/Modules/BiddingManagement/app/Enums/BidSubmissionStatus.php deleted file mode 100644 index 3e335d9..0000000 --- a/Modules/BiddingManagement/app/Enums/BidSubmissionStatus.php +++ /dev/null @@ -1,31 +0,0 @@ - 'Submitted', - self::Shortlisted => 'Shortlisted', - self::Rejected => 'Rejected', - self::Awarded => 'Awarded', - }; - } - - public function allowedTransitions(): array - { - return match ($this) { - self::Submitted => [self::Shortlisted, self::Rejected], - self::Shortlisted => [self::Awarded, self::Rejected], - self::Rejected => [], - self::Awarded => [], - }; - } -} diff --git a/Modules/BiddingManagement/app/Enums/EvaluationMode.php b/Modules/BiddingManagement/app/Enums/EvaluationMode.php deleted file mode 100644 index 36299bd..0000000 --- a/Modules/BiddingManagement/app/Enums/EvaluationMode.php +++ /dev/null @@ -1,17 +0,0 @@ - 'Simple Selection', - self::Scored => 'Weighted Scorecard', - }; - } -} diff --git a/Modules/BiddingManagement/app/Events/BidAwarded.php b/Modules/BiddingManagement/app/Events/BidAwarded.php deleted file mode 100644 index 17d8cc9..0000000 --- a/Modules/BiddingManagement/app/Events/BidAwarded.php +++ /dev/null @@ -1,14 +0,0 @@ -authorizeBiddingManagement(); - - abort_unless( - in_array($bid->status, [BidPackageStatus::Open, BidPackageStatus::Evaluating]), - 403, - 'Award can only be made from an open or evaluating package.' - ); - - $validated = $request->validate([ - 'submission_id' => 'required|string', - 'notes' => 'nullable|string|max:1000', - ]); - - $submission = BidSubmission::findByUlid($validated['submission_id']); - - abort_if(!$submission, 404, 'Submission not found.'); - abort_unless( - $submission->invitation->bid_package_id === $bid->id, - 422, - 'Submission does not belong to this bid package.' - ); - - $award = BidAward::createAward( - $bid, - $submission, - Auth::user(), - $validated['notes'] ?? null - ); - - BidAwarded::dispatch($award); - - return redirect() - ->route('bids.show', $bid) - ->with('success', 'Contract awarded successfully. Project contractor has been updated.'); - } -} diff --git a/Modules/BiddingManagement/app/Http/Controllers/BidInvitationController.php b/Modules/BiddingManagement/app/Http/Controllers/BidInvitationController.php deleted file mode 100644 index 78a92fd..0000000 --- a/Modules/BiddingManagement/app/Http/Controllers/BidInvitationController.php +++ /dev/null @@ -1,95 +0,0 @@ -authorizeBiddingManagement(); - - abort_unless( - in_array($bid->status, [BidPackageStatus::Draft, BidPackageStatus::Open]), - 403, - 'Invitations can only be added to draft or open packages.' - ); - - $validated = $request->validate([ - 'contractor_ids' => 'required|array|min:1', - 'contractor_ids.*' => 'required|string', - ]); - - $added = 0; - foreach ($validated['contractor_ids'] as $ulid) { - $contractorId = Contractor::resolveUlidToId($ulid); - if (!$contractorId) { - continue; - } - - // Skip duplicates - $exists = $bid->invitations()->where('contractor_id', $contractorId)->exists(); - if ($exists) { - continue; - } - - $bid->invitations()->create([ - 'contractor_id' => $contractorId, - 'status' => 'invited', - 'invited_at' => now(), - ]); - $added++; - } - - return back()->with('success', "{$added} contractor(s) invited."); - } - - public function destroy(BidInvitation $invitation) - { - $this->authorizeBiddingManagement(); - - abort_unless( - in_array($invitation->package->status, [BidPackageStatus::Draft, BidPackageStatus::Open]), - 403, - 'Cannot remove invitations at this stage.' - ); - - abort_if($invitation->submission()->exists(), 422, 'Cannot remove an invitation that has a submission.'); - - $invitation->delete(); - - return back()->with('success', 'Invitation removed.'); - } - - public function accept(BidInvitation $invitation) - { - abort_unless($invitation->package->status === BidPackageStatus::Open, 403, 'Bid package is not open.'); - abort_if($invitation->status === 'declined', 422, 'You have already declined this invitation.'); - - $invitation->accept(); - - return back()->with('success', 'You have accepted the bid invitation.'); - } - - public function decline(Request $request, BidInvitation $invitation) - { - abort_unless($invitation->package->status === BidPackageStatus::Open, 403, 'Bid package is not open.'); - - $validated = $request->validate([ - 'reason' => 'nullable|string|max:500', - ]); - - $invitation->decline($validated['reason'] ?? null); - - return back()->with('success', 'You have declined the bid invitation.'); - } -} diff --git a/Modules/BiddingManagement/app/Http/Controllers/BidPackageController.php b/Modules/BiddingManagement/app/Http/Controllers/BidPackageController.php deleted file mode 100644 index 8fefc44..0000000 --- a/Modules/BiddingManagement/app/Http/Controllers/BidPackageController.php +++ /dev/null @@ -1,282 +0,0 @@ -isBiddingManager($user) || $this->isContractorUser($user), - 403, - 'Only Project Manager, executive, or invited contractor roles can access bidding.' - ); - - $query = BidPackage::with(['project:id,name,code', 'creator:id,name', 'invitations:id,bid_package_id,ulid,contractor_id,status']) - ->withCount(['invitations', 'submissions']); - - if ($search = $request->search) { - $query->where('title', 'like', "%{$search}%"); - } - - if ($status = $request->status) { - $query->where('status', $status); - } - - if ($mode = $request->evaluation_mode) { - $query->where('evaluation_mode', $mode); - } - - if ($projectUlid = $request->project) { - $projectId = Project::resolveUlidToId($projectUlid); - $query->where('project_id', $projectId); - } - - // Contractor Admin users should ONLY see published (non-draft) bid packages where their company is invited - if (! $this->isBiddingManager($user) && $this->isContractorUser($user)) { - $query->where('status', '!=', BidPackageStatus::Draft->value) - ->whereHas('invitations', function ($q) use ($user) { - $q->where('contractor_id', $user->contractor_id); - }); - } - - $packages = $query->latest()->paginate(15)->withQueryString(); - - return Inertia::render('BiddingManagement::Bids/Index', [ - 'packages' => $packages, - 'filters' => $request->only(['search', 'status', 'evaluation_mode', 'project']), - 'projects' => Project::select('id', 'ulid', 'name', 'code')->get(), - ]); - } - - public function create() - { - $this->authorizePackageManagement(); - - return Inertia::render('BiddingManagement::Bids/Create', [ - 'projects' => Project::where('current_wizard_step', '>=', 7)->select('id', 'ulid', 'name', 'code')->get(), - ]); - } - - public function store(Request $request) - { - $this->authorizePackageManagement(); - - $validated = $request->validate([ - 'project_id' => 'required|string', - 'title' => 'required|string|max:255', - 'description' => 'nullable|string', - 'evaluation_mode' => 'required|in:simple,scored', - 'submission_deadline' => 'nullable|date', - 'validity_period' => 'nullable|date', - 'instructions' => 'nullable|string', - 'criteria' => 'nullable|array', - 'criteria.*.name' => 'required_with:criteria|string|max:100', - 'criteria.*.weight' => 'required_with:criteria|numeric|min:0|max:100', - 'criteria.*.description' => 'nullable|string', - ]); - - $validated['project_id'] = Project::resolveUlidToId($validated['project_id']); - $project = Project::find($validated['project_id']); - abort_unless($project, 422, 'The selected project could not be found.'); - abort_unless( - $project->current_wizard_step >= 7, - 422, - 'The project must finish initialization before a bid package can be created.' - ); - abort_unless( - $this->isExecutive(Auth::user()) - || $project->personnel()->where('users.id', Auth::id())->wherePivot('role', 'pm')->exists(), - 403, - 'Only the assigned Project Manager or an executive can create this bid package.' - ); - $validated['created_by'] = Auth::id(); - - $criteria = $validated['criteria'] ?? []; - unset($validated['criteria']); - - if ($validated['evaluation_mode'] === 'scored' && count($criteria) > 0) { - $totalWeight = array_sum(array_column($criteria, 'weight')); - if ($totalWeight > 100) { - return back()->withErrors(['criteria' => "Total criteria weight ({$totalWeight}%) exceeds 100%."]); - } - } - - $package = BidPackage::create($validated); - - foreach ($criteria as $index => $criterion) { - $package->criteria()->create([ - 'name' => $criterion['name'], - 'weight' => $criterion['weight'], - 'description' => $criterion['description'] ?? null, - 'sort_order' => $index, - ]); - } - - return redirect()->route('bids.show', $package)->with('success', 'Bid package created.'); - } - - public function show(BidPackage $bid) - { - $user = Auth::user(); - abort_unless( - $this->isBiddingManager($user) || $this->isContractorUser($user), - 403, - 'Only Project Manager, executive, or invited contractor roles can access bidding.' - ); - if (! $this->isBiddingManager($user) && $this->isContractorUser($user)) { - abort_if($bid->status === BidPackageStatus::Draft, 403, 'This bid package is currently in draft state.'); - abort_unless($bid->invitations()->where('contractor_id', $user->contractor_id)->exists(), 403, 'Your company has not been invited to this bid package.'); - } - - $bid->load([ - 'project:id,ulid,name,code', - 'creator:id,name', - 'criteria', - 'invitations.contractor:id,ulid,company_name,specialization', - 'invitations.submission.scores', - 'invitations.submission.documents', - 'award.submission.invitation.contractor:id,company_name', - 'award.awardedBy:id,name', - 'documents', - ]); - - $contractors = Contractor::where('status', 'active') - ->select('id', 'ulid', 'company_name', 'specialization') - ->get(); - - return Inertia::render('BiddingManagement::Bids/Show', [ - 'package' => $bid, - 'contractors' => $contractors, - ]); - } - - public function edit(BidPackage $bid) - { - $this->authorizePackageManagement(); - - abort_unless($bid->status === BidPackageStatus::Draft, 403, 'Only draft packages can be edited.'); - - $bid->load('criteria'); - - return Inertia::render('BiddingManagement::Bids/Edit', [ - 'package' => $bid, - 'projects' => Project::where('current_wizard_step', '>=', 7)->select('id', 'ulid', 'name', 'code')->get(), - ]); - } - - public function update(Request $request, BidPackage $bid) - { - $this->authorizePackageManagement(); - - abort_unless($bid->status === BidPackageStatus::Draft, 403, 'Only draft packages can be edited.'); - - $validated = $request->validate([ - 'title' => 'required|string|max:255', - 'description' => 'nullable|string', - 'evaluation_mode' => 'required|in:simple,scored', - 'submission_deadline' => 'nullable|date', - 'validity_period' => 'nullable|date', - 'instructions' => 'nullable|string', - 'criteria' => 'nullable|array', - 'criteria.*.name' => 'required_with:criteria|string|max:100', - 'criteria.*.weight' => 'required_with:criteria|numeric|min:0|max:100', - 'criteria.*.description' => 'nullable|string', - ]); - - $criteria = $validated['criteria'] ?? []; - unset($validated['criteria']); - - $bid->update($validated); - - // Replace criteria wholesale - $bid->criteria()->delete(); - foreach ($criteria as $index => $criterion) { - $bid->criteria()->create([ - 'name' => $criterion['name'], - 'weight' => $criterion['weight'], - 'description' => $criterion['description'] ?? null, - 'sort_order' => $index, - ]); - } - - return redirect()->route('bids.show', $bid)->with('success', 'Bid package updated.'); - } - - public function destroy(BidPackage $bid) - { - $this->authorizePackageManagement(); - - abort_unless($bid->status === BidPackageStatus::Draft, 403, 'Only draft packages can be deleted.'); - - $bid->delete(); - - return redirect()->route('bids.index')->with('success', 'Bid package deleted.'); - } - - public function publish(BidPackage $bid) - { - $this->authorizePackageManagement(); - - try { - $bid->transitionTo(BidPackageStatus::Open); - BidPackageOpened::dispatch($bid); - } catch (\InvalidArgumentException $e) { - return back()->with('error', $e->getMessage()); - } - - return back()->with('success', 'Bid package is now open for submissions.'); - } - - public function startEvaluation(BidPackage $bid) - { - $this->authorizePackageManagement(); - - try { - $bid->transitionTo(BidPackageStatus::Evaluating); - } catch (\InvalidArgumentException $e) { - return back()->with('error', $e->getMessage()); - } - - return back()->with('success', 'Evaluation phase started.'); - } - - public function cancel(BidPackage $bid) - { - $this->authorizePackageManagement(); - - try { - $bid->transitionTo(BidPackageStatus::Cancelled); - } catch (\InvalidArgumentException $e) { - return back()->with('error', $e->getMessage()); - } - - return back()->with('success', 'Bid package cancelled.'); - } - - private function authorizePackageManagement(): void - { - $this->authorizeBiddingManagement(); - } - - private function isContractorUser($user): bool - { - return $user !== null - && ! $this->isBiddingManager($user) - && ($user->user_type === 'contractor' || $user->contractor_id !== null); - } -} diff --git a/Modules/BiddingManagement/app/Http/Controllers/BidScoreController.php b/Modules/BiddingManagement/app/Http/Controllers/BidScoreController.php deleted file mode 100644 index a7a23d1..0000000 --- a/Modules/BiddingManagement/app/Http/Controllers/BidScoreController.php +++ /dev/null @@ -1,48 +0,0 @@ -authorizeBiddingManagement(); - - $package = $submission->invitation->package; - - abort_unless($package->status === BidPackageStatus::Evaluating, 403, 'Package is not in evaluation phase.'); - - $validated = $request->validate([ - 'scores' => 'required|array', - 'scores.*.criteria_id' => 'required|integer|exists:bid_criteria,id', - 'scores.*.score' => 'required|numeric|min:0|max:100', - 'scores.*.notes' => 'nullable|string|max:500', - ]); - - foreach ($validated['scores'] as $entry) { - BidScore::updateOrCreate( - [ - 'bid_submission_id' => $submission->id, - 'bid_criteria_id' => $entry['criteria_id'], - ], - [ - 'evaluated_by' => Auth::id(), - 'score' => $entry['score'], - 'notes' => $entry['notes'] ?? null, - ] - ); - } - - return back()->with('success', 'Scores saved.'); - } -} diff --git a/Modules/BiddingManagement/app/Http/Controllers/BidSubmissionController.php b/Modules/BiddingManagement/app/Http/Controllers/BidSubmissionController.php deleted file mode 100644 index 34718bb..0000000 --- a/Modules/BiddingManagement/app/Http/Controllers/BidSubmissionController.php +++ /dev/null @@ -1,152 +0,0 @@ -package->status === BidPackageStatus::Open, 403, 'Bid package is not open.'); - abort_if($invitation->submission()->exists(), 422, 'You have already submitted a bid.'); - - $invitation->load('package.project:id,name,code', 'package.criteria'); - - return Inertia::render('BiddingManagement::Bids/Contractor/Submit', [ - 'invitation' => $invitation, - ]); - } - - public function store(Request $request, BidInvitation $invitation) - { - abort_unless($invitation->package->status === BidPackageStatus::Open, 403, 'Bid package is not open.'); - abort_if($invitation->submission()->exists(), 422, 'You have already submitted a bid.'); - - $validated = $request->validate([ - 'submitted_price' => 'required|numeric|min:0', - 'completion_days' => 'required|integer|min:1', - 'validity_date' => 'nullable|date|after_or_equal:today', - 'technical_notes' => 'nullable|string', - 'documents' => 'nullable|array', - 'documents.*' => 'file|max:20480|mimes:pdf', - ]); - - // Accept invitation if still invited - if ($invitation->status === 'invited') { - $invitation->accept(); - } - - $documents = $validated['documents'] ?? []; - unset($validated['documents']); - - $validated['bid_invitation_id'] = $invitation->id; - $validated['submitted_at'] = now(); - - $submission = BidSubmission::create($validated); - - // Handle document uploads via DocumentManagement pattern - foreach ($documents as $file) { - $path = $file->store('bid-submissions', 'public'); - $document = $submission->documents()->create([ - 'title' => $file->getClientOriginalName(), - 'category' => 'bid_document', - 'documentable_type' => BidSubmission::class, - 'documentable_id' => $submission->id, - 'uploaded_by' => Auth::id(), - 'current_file_path' => $path, - 'current_file_name' => $file->getClientOriginalName(), - 'mime_type' => $file->getMimeType(), - 'file_size' => $file->getSize(), - 'version_count' => 1, - ]); - } - - BidSubmitted::dispatch($submission); - - return redirect() - ->route('bids.my') - ->with('success', 'Your bid has been submitted successfully.'); - } - - public function show(BidSubmission $submission) - { - $submission->load([ - 'invitation.contractor:id,ulid,company_name,contact_person,email,specialization', - 'invitation.package:id,ulid,title,evaluation_mode', - 'invitation.package.criteria', - 'scores.criteria', - 'scores.evaluator:id,name', - 'documents', - ]); - - return Inertia::render('BiddingManagement::Bids/Submission/Show', [ - 'submission' => $submission, - ]); - } - - public function shortlist(BidSubmission $submission) - { - $this->authorizeBiddingManagement(); - - try { - $submission->transitionTo(BidSubmissionStatus::Shortlisted); - } catch (\InvalidArgumentException $e) { - return back()->with('error', $e->getMessage()); - } - - return back()->with('success', 'Submission shortlisted.'); - } - - public function reject(BidSubmission $submission) - { - $this->authorizeBiddingManagement(); - - try { - $submission->transitionTo(BidSubmissionStatus::Rejected); - } catch (\InvalidArgumentException $e) { - return back()->with('error', $e->getMessage()); - } - - return back()->with('success', 'Submission rejected.'); - } - - public function myBids() - { - $user = Auth::user(); - $contractor = Contractor::where('id', $user->contractor_id ?? null)->first(); - - if (! $contractor) { - return redirect()->route('bids.index')->with('warning', 'My Bids portal is dedicated to invited contractor accounts. Internal managers can manage all bid packages here.'); - } - - $invitations = $contractor->bidInvitations() - ->whereHas('package', function ($query) { - $query->where('status', '!=', BidPackageStatus::Draft->value); - }) - ->with([ - 'package:id,ulid,project_id,title,submission_deadline,status,evaluation_mode', - 'package.project:id,name,code', - 'submission:id,ulid,bid_invitation_id,submitted_price,completion_days,status,submitted_at', - ]) - ->latest() - ->get(); - - return Inertia::render('BiddingManagement::Bids/Contractor/MyBids', [ - 'invitations' => $invitations, - 'contractor' => $contractor, - ]); - } -} diff --git a/Modules/BiddingManagement/app/Models/BidAward.php b/Modules/BiddingManagement/app/Models/BidAward.php deleted file mode 100644 index e0bcc1d..0000000 --- a/Modules/BiddingManagement/app/Models/BidAward.php +++ /dev/null @@ -1,78 +0,0 @@ - 'datetime', - ]; - } - - // --- Relationships --- - - public function package(): BelongsTo - { - return $this->belongsTo(BidPackage::class, 'bid_package_id'); - } - - public function submission(): BelongsTo - { - return $this->belongsTo(BidSubmission::class, 'bid_submission_id'); - } - - public function awardedBy(): BelongsTo - { - return $this->belongsTo(User::class, 'awarded_by'); - } - - // --- Observer logic (called from controller to keep it explicit) --- - - public static function createAward(BidPackage $package, BidSubmission $winning, User $awarder, ?string $notes = null): self - { - // Mark package as awarded - $package->transitionTo(BidPackageStatus::Awarded); - - // Mark winning submission as awarded - $winning->transitionTo(BidSubmissionStatus::Awarded); - - // Reject all other non-rejected submissions - $package->submissions() - ->whereNotIn('bid_submissions.id', [$winning->id]) - ->whereNotIn('bid_submissions.status', ['rejected']) - ->get() - ->each(fn ($sub) => $sub->update(['status' => BidSubmissionStatus::Rejected])); - - // Link contractor to the project and transition project status from under_bidding -> in_progress - $contractor = $winning->contractor; - if ($package->project) { - $package->project->update([ - 'contractor_id' => $contractor->id, - 'status' => \Modules\ProjectManagement\Enums\ProjectStatus::InProgress, - ]); - } - - return self::create([ - 'bid_package_id' => $package->id, - 'bid_submission_id' => $winning->id, - 'awarded_by' => $awarder->id, - 'awarded_at' => now(), - 'notes' => $notes, - ]); - } -} diff --git a/Modules/BiddingManagement/app/Models/BidCriteria.php b/Modules/BiddingManagement/app/Models/BidCriteria.php deleted file mode 100644 index 8624c37..0000000 --- a/Modules/BiddingManagement/app/Models/BidCriteria.php +++ /dev/null @@ -1,34 +0,0 @@ - 'decimal:2', - 'sort_order' => 'integer', - ]; - } - - public function package(): BelongsTo - { - return $this->belongsTo(BidPackage::class, 'bid_package_id'); - } - - public function scores(): HasMany - { - return $this->hasMany(BidScore::class); - } -} diff --git a/Modules/BiddingManagement/app/Models/BidInvitation.php b/Modules/BiddingManagement/app/Models/BidInvitation.php deleted file mode 100644 index 934ea4c..0000000 --- a/Modules/BiddingManagement/app/Models/BidInvitation.php +++ /dev/null @@ -1,64 +0,0 @@ - 'datetime', - 'responded_at' => 'datetime', - ]; - } - - public function package(): BelongsTo - { - return $this->belongsTo(BidPackage::class, 'bid_package_id'); - } - - public function contractor(): BelongsTo - { - return $this->belongsTo(Contractor::class); - } - - public function submission(): HasOne - { - return $this->hasOne(BidSubmission::class); - } - - public function accept(): void - { - $this->update([ - 'status' => 'accepted', - 'responded_at' => now(), - ]); - } - - public function decline(string $reason = null): void - { - $this->update([ - 'status' => 'declined', - 'responded_at' => now(), - 'decline_reason' => $reason, - ]); - } - - public function getHasSubmittedAttribute(): bool - { - return $this->submission()->exists(); - } -} diff --git a/Modules/BiddingManagement/app/Models/BidPackage.php b/Modules/BiddingManagement/app/Models/BidPackage.php deleted file mode 100644 index e296789..0000000 --- a/Modules/BiddingManagement/app/Models/BidPackage.php +++ /dev/null @@ -1,126 +0,0 @@ - BidPackageStatus::class, - 'evaluation_mode' => EvaluationMode::class, - 'submission_deadline' => 'date', - 'validity_period' => 'date', - ]; - } - - // --- Relationships --- - - public function project(): BelongsTo - { - return $this->belongsTo(Project::class); - } - - public function creator(): BelongsTo - { - return $this->belongsTo(User::class, 'created_by'); - } - - public function criteria(): HasMany - { - return $this->hasMany(BidCriteria::class)->orderBy('sort_order'); - } - - public function invitations(): HasMany - { - return $this->hasMany(BidInvitation::class); - } - - public function submissions(): HasManyThrough - { - return $this->hasManyThrough(BidSubmission::class, BidInvitation::class); - } - - public function award(): HasOne - { - return $this->hasOne(BidAward::class); - } - - public function documents(): MorphMany - { - return $this->morphMany(Document::class, 'documentable'); - } - - // --- State Machine --- - - public function transitionTo(BidPackageStatus $newStatus): void - { - $current = $this->status; - - if (!in_array($newStatus, $current->allowedTransitions())) { - throw new \InvalidArgumentException( - "Cannot transition from {$current->label()} to {$newStatus->label()}" - ); - } - - // Guard: must have at least one submission before evaluating - if ($newStatus === BidPackageStatus::Evaluating) { - if ($this->submissions()->count() === 0) { - throw new \InvalidArgumentException('Cannot start evaluation without any submissions.'); - } - } - - $this->update(['status' => $newStatus]); - } - - // --- Computed Helpers --- - - public function totalCriteriaWeight(): float - { - return (float) $this->criteria()->sum('weight'); - } - - public function isFullyScored(): bool - { - if ($this->evaluation_mode !== EvaluationMode::Scored) { - return true; - } - - $criteriaCount = $this->criteria()->count(); - if ($criteriaCount === 0) { - return false; - } - - return $this->submissions() - ->where('bid_submissions.status', '!=', 'rejected') - ->get() - ->every(fn ($sub) => $sub->scores()->count() >= $criteriaCount); - } - - public function getIsDeadlinePassedAttribute(): bool - { - return $this->submission_deadline && $this->submission_deadline->isPast(); - } -} diff --git a/Modules/BiddingManagement/app/Models/BidScore.php b/Modules/BiddingManagement/app/Models/BidScore.php deleted file mode 100644 index dde5b27..0000000 --- a/Modules/BiddingManagement/app/Models/BidScore.php +++ /dev/null @@ -1,36 +0,0 @@ - 'decimal:2', - ]; - } - - public function submission(): BelongsTo - { - return $this->belongsTo(BidSubmission::class, 'bid_submission_id'); - } - - public function criteria(): BelongsTo - { - return $this->belongsTo(BidCriteria::class, 'bid_criteria_id'); - } - - public function evaluator(): BelongsTo - { - return $this->belongsTo(User::class, 'evaluated_by'); - } -} diff --git a/Modules/BiddingManagement/app/Models/BidSubmission.php b/Modules/BiddingManagement/app/Models/BidSubmission.php deleted file mode 100644 index 7925d51..0000000 --- a/Modules/BiddingManagement/app/Models/BidSubmission.php +++ /dev/null @@ -1,89 +0,0 @@ - BidSubmissionStatus::class, - 'submitted_price' => 'decimal:2', - 'completion_days' => 'integer', - 'validity_date' => 'date', - 'submitted_at' => 'datetime', - ]; - } - - // --- Relationships --- - - public function invitation(): BelongsTo - { - return $this->belongsTo(BidInvitation::class, 'bid_invitation_id'); - } - - public function scores(): HasMany - { - return $this->hasMany(BidScore::class); - } - - public function documents(): MorphMany - { - return $this->morphMany(Document::class, 'documentable'); - } - - // --- State Machine --- - - public function transitionTo(BidSubmissionStatus $newStatus): void - { - $current = $this->status; - - if (!in_array($newStatus, $current->allowedTransitions())) { - throw new \InvalidArgumentException( - "Cannot transition from {$current->label()} to {$newStatus->label()}" - ); - } - - $this->update(['status' => $newStatus]); - } - - // --- Computed --- - - public function getComputedScoreAttribute(): float - { - $criteria = $this->invitation->package->criteria; - if ($criteria->isEmpty()) { - return 0.0; - } - - $total = 0.0; - foreach ($this->scores as $score) { - $criterion = $criteria->firstWhere('id', $score->bid_criteria_id); - if ($criterion) { - $total += ($score->score / 100) * $criterion->weight; - } - } - - return round($total, 2); - } - - public function getContractorAttribute() - { - return $this->invitation->contractor; - } -} diff --git a/Modules/BiddingManagement/app/Providers/BiddingManagementServiceProvider.php b/Modules/BiddingManagement/app/Providers/BiddingManagementServiceProvider.php deleted file mode 100644 index 4d51dc0..0000000 --- a/Modules/BiddingManagement/app/Providers/BiddingManagementServiceProvider.php +++ /dev/null @@ -1,17 +0,0 @@ -mapApiRoutes(); - $this->mapWebRoutes(); - } - - protected function mapWebRoutes(): void - { - Route::middleware('web')->group(module_path($this->name, '/routes/web.php')); - } - - protected function mapApiRoutes(): void - { - Route::middleware('api')->prefix('api')->name('api.')->group(module_path($this->name, '/routes/api.php')); - } -} diff --git a/Modules/BiddingManagement/app/Traits/AuthorizesBiddingManagement.php b/Modules/BiddingManagement/app/Traits/AuthorizesBiddingManagement.php deleted file mode 100644 index c8cdf8d..0000000 --- a/Modules/BiddingManagement/app/Traits/AuthorizesBiddingManagement.php +++ /dev/null @@ -1,30 +0,0 @@ -isBiddingManager(Auth::user()), - 403, - 'Only Project Manager and executive roles can perform bidding actions.' - ); - } - - protected function isBiddingManager(?User $user): bool - { - return $user !== null - && $user->hasAnyRole(['Project Manager', 'admin', 'Super Admin']); - } - - protected function isExecutive(?User $user): bool - { - return $user !== null - && ($user->user_type === 'admin' || $user->hasAnyRole(['admin', 'Super Admin'])); - } -} diff --git a/Modules/BiddingManagement/composer.json b/Modules/BiddingManagement/composer.json deleted file mode 100644 index 776edb4..0000000 --- a/Modules/BiddingManagement/composer.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "nwidart/biddingmanagement", - "description": "Contractor bidding and tender management", - "authors": [ - { - "name": "Nicolas Widart", - "email": "n.widart@gmail.com" - } - ], - "extra": { - "laravel": { - "providers": [], - "aliases": {} - } - }, - "autoload": { - "psr-4": { - "Modules\\BiddingManagement\\": "app/", - "Modules\\BiddingManagement\\Database\\Factories\\": "database/factories/", - "Modules\\BiddingManagement\\Database\\Seeders\\": "database/seeders/" - } - }, - "autoload-dev": { - "psr-4": { - "Modules\\BiddingManagement\\Tests\\": "tests/" - } - } -} diff --git a/Modules/BiddingManagement/database/migrations/2026_05_23_000001_create_bidding_tables.php b/Modules/BiddingManagement/database/migrations/2026_05_23_000001_create_bidding_tables.php deleted file mode 100644 index 8f56320..0000000 --- a/Modules/BiddingManagement/database/migrations/2026_05_23_000001_create_bidding_tables.php +++ /dev/null @@ -1,101 +0,0 @@ -id(); - $table->string('ulid')->unique(); - $table->foreignId('project_id')->constrained()->cascadeOnDelete(); - $table->foreignId('created_by')->constrained('users')->cascadeOnDelete(); - $table->string('title'); - $table->text('description')->nullable(); - $table->string('status')->default('draft'); // draft, open, evaluating, awarded, cancelled - $table->string('evaluation_mode')->default('simple'); // simple, scored - $table->date('submission_deadline')->nullable(); - $table->date('validity_period')->nullable(); // bid price validity - $table->text('instructions')->nullable(); - $table->timestamps(); - $table->softDeletes(); - }); - - // Scoring criteria for scored-mode packages - Schema::create('bid_criteria', function (Blueprint $table) { - $table->id(); - $table->foreignId('bid_package_id')->constrained()->cascadeOnDelete(); - $table->string('name'); // e.g., Price, Technical Capability, Experience - $table->decimal('weight', 5, 2); // percentage weight (0–100) - $table->text('description')->nullable(); - $table->unsignedTinyInteger('sort_order')->default(0); - $table->timestamps(); - }); - - // Which contractors are invited to bid - Schema::create('bid_invitations', function (Blueprint $table) { - $table->id(); - $table->string('ulid')->unique(); - $table->foreignId('bid_package_id')->constrained()->cascadeOnDelete(); - $table->foreignId('contractor_id')->constrained()->cascadeOnDelete(); - $table->string('status')->default('invited'); // invited, accepted, declined - $table->timestamp('invited_at')->nullable(); - $table->timestamp('responded_at')->nullable(); - $table->text('decline_reason')->nullable(); - $table->timestamps(); - $table->unique(['bid_package_id', 'contractor_id']); - }); - - // Contractor's submitted proposal - Schema::create('bid_submissions', function (Blueprint $table) { - $table->id(); - $table->string('ulid')->unique(); - $table->foreignId('bid_invitation_id')->constrained()->cascadeOnDelete(); - $table->decimal('submitted_price', 18, 2); - $table->unsignedInteger('completion_days'); - $table->date('validity_date')->nullable(); - $table->text('technical_notes')->nullable(); - $table->string('status')->default('submitted'); // submitted, shortlisted, rejected, awarded - $table->timestamp('submitted_at')->useCurrent(); - $table->timestamps(); - }); - - // Evaluator scores per criterion per submission (scored mode only) - Schema::create('bid_scores', function (Blueprint $table) { - $table->id(); - $table->foreignId('bid_submission_id')->constrained()->cascadeOnDelete(); - $table->foreignId('bid_criteria_id')->constrained('bid_criteria')->cascadeOnDelete(); - $table->foreignId('evaluated_by')->constrained('users')->cascadeOnDelete(); - $table->decimal('score', 5, 2); // 0–100 - $table->text('notes')->nullable(); - $table->timestamps(); - $table->unique(['bid_submission_id', 'bid_criteria_id']); - }); - - // The winning bid award - Schema::create('bid_awards', function (Blueprint $table) { - $table->id(); - $table->string('ulid')->unique(); - $table->foreignId('bid_package_id')->constrained()->cascadeOnDelete(); - $table->foreignId('bid_submission_id')->constrained()->cascadeOnDelete(); - $table->foreignId('awarded_by')->constrained('users')->cascadeOnDelete(); - $table->timestamp('awarded_at')->useCurrent(); - $table->text('notes')->nullable(); - $table->timestamps(); - }); - } - - public function down(): void - { - Schema::dropIfExists('bid_awards'); - Schema::dropIfExists('bid_scores'); - Schema::dropIfExists('bid_submissions'); - Schema::dropIfExists('bid_invitations'); - Schema::dropIfExists('bid_criteria'); - Schema::dropIfExists('bid_packages'); - } -}; diff --git a/Modules/BiddingManagement/module.json b/Modules/BiddingManagement/module.json deleted file mode 100644 index e788528..0000000 --- a/Modules/BiddingManagement/module.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "BiddingManagement", - "alias": "biddingmanagement", - "description": "Contractor bidding and tender management for projects", - "keywords": [], - "priority": 0, - "providers": [ - "Modules\\BiddingManagement\\Providers\\BiddingManagementServiceProvider" - ], - "files": [] -} diff --git a/Modules/BiddingManagement/package.json b/Modules/BiddingManagement/package.json deleted file mode 100644 index d6fbfc8..0000000 --- a/Modules/BiddingManagement/package.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "private": true, - "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build" - }, - "devDependencies": { - "axios": "^1.1.2", - "laravel-vite-plugin": "^0.7.5", - "sass": "^1.69.5", - "postcss": "^8.3.7", - "vite": "^4.0.0" - } -} diff --git a/Modules/BiddingManagement/resources/js/Pages/Bids/Contractor/MyBids.tsx b/Modules/BiddingManagement/resources/js/Pages/Bids/Contractor/MyBids.tsx deleted file mode 100644 index 268813c..0000000 --- a/Modules/BiddingManagement/resources/js/Pages/Bids/Contractor/MyBids.tsx +++ /dev/null @@ -1,261 +0,0 @@ -import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout'; -import { Head, Link, router, usePage } from '@inertiajs/react'; -import { Badge } from '@/Components/ui/badge'; -import { Button } from '@/Components/ui/button'; -import { Card, CardContent, CardHeader, CardTitle } from '@/Components/ui/card'; -import { - Table, TableBody, TableCell, TableHead, TableHeader, TableRow, -} from '@/Components/ui/table'; -import { Gavel, Send, Trophy, CalendarClock, CheckCircle2 } from 'lucide-react'; -import { PageProps } from '@/types'; - -interface Invitation { - id: number; ulid: string; status: string; invited_at: string; - package: { - id: number; ulid: string; title: string; status: string; evaluation_mode: string; - submission_deadline?: string; - project: { id: number; name: string; code: string } | null; - }; - submission?: { - id: number; ulid: string; submitted_price: string; completion_days: number; - status: string; submitted_at: string; - }; -} - -interface Props extends PageProps { - invitations: Invitation[]; - contractor: { id: number; company_name: string }; -} - -const fmt = (v: string) => new Intl.NumberFormat('en-PH', { style: 'currency', currency: 'PHP' }).format(Number(v)); -const statusLabel = (s: string) => s.charAt(0).toUpperCase() + s.slice(1); - -const invStatusVariant = (s: string): 'default' | 'secondary' | 'destructive' | 'outline' => { - switch (s) { - case 'accepted': return 'default'; - case 'declined': return 'destructive'; - default: return 'outline'; - } -}; - -const subStatusVariant = (s: string): 'default' | 'secondary' | 'destructive' | 'outline' => { - switch (s) { - case 'awarded': return 'default'; - case 'shortlisted': return 'secondary'; - case 'rejected': return 'destructive'; - default: return 'outline'; - } -}; - -const pkgStatusVariant = (s: string): 'default' | 'secondary' | 'destructive' | 'outline' => { - switch (s) { - case 'open': return 'default'; - case 'evaluating': return 'secondary'; - case 'awarded': return 'default'; - case 'cancelled': return 'destructive'; - default: return 'outline'; - } -}; - -export default function MyBids({ invitations, contractor }: Props) { - const { flash } = usePage().props; - - const pending = invitations.filter(i => i.status === 'invited' && !i.submission && i.package.status === 'open'); - const submitted = invitations.filter(i => i.submission); - const awardedCount = submitted.filter(i => i.submission?.status === 'awarded').length; - const other = invitations.filter(i => i.status === 'declined' || (i.package.status !== 'open' && !i.submission)); - - return ( - -
-
- -
-
-

Contractor Portal — My Bids

-

{contractor.company_name}

-
-
- - } - > - - -
-
- {flash?.success &&
{flash.success}
} - - {/* Metric Cards Banner */} -
- -
Total Invitations
-
{invitations.length}
-
- -
Action Required
-
{pending.length}
-
- -
Bids Submitted
-
{submitted.length}
-
- -
Contracts Awarded
-
{awardedCount}
-
-
- - {/* Action Required Section */} - {pending.length > 0 && ( - - - - - Action Required — Open Tender Invitations ({pending.length}) - - - - {pending.map(inv => ( -
-
-

{inv.package.title}

-

- {inv.package.project - ? Project: {inv.package.project.name} ({inv.package.project.code}) - : No project} -

- {inv.package.submission_deadline && ( -
- - Submission Deadline: {new Date(inv.package.submission_deadline).toLocaleDateString()} -
- )} -
-
- - - Submit Proposal - -
-
- ))} -
-
- )} - - {/* Submitted Bids */} - - - - - Submitted Bids & Proposals - - - - - - - Bid Package - Project - Package Status - My Quoted Price - Proposal Status - Submitted Date - - - - {submitted.length === 0 ? ( - No bid proposals submitted yet. - ) : submitted.map(inv => ( - - -
- {inv.submission?.status === 'awarded' && } - {inv.package.title} -
-
- - {inv.package.project?.name ?? Unassigned} - - - - {inv.package.status} - - - - {inv.submission ? fmt(inv.submission.submitted_price) : '—'} - - - {inv.submission && ( - - {statusLabel(inv.submission.status)} - - )} - - - {inv.submission ? new Date(inv.submission.submitted_at).toLocaleDateString() : '—'} - -
- ))} -
-
-
-
- - {/* Other Invitations */} - {other.length > 0 && ( - - - Other Invitations - - - - - - Bid Package - Project - My Response - Package Status - - - - {other.map(inv => ( - - {inv.package.title} - {inv.package.project?.name ?? '—'} - - {statusLabel(inv.status)} - - - {statusLabel(inv.package.status)} - - - ))} - -
-
-
- )} -
-
-
- ); -} diff --git a/Modules/BiddingManagement/resources/js/Pages/Bids/Contractor/Submit.tsx b/Modules/BiddingManagement/resources/js/Pages/Bids/Contractor/Submit.tsx deleted file mode 100644 index c019c0e..0000000 --- a/Modules/BiddingManagement/resources/js/Pages/Bids/Contractor/Submit.tsx +++ /dev/null @@ -1,238 +0,0 @@ -import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout'; -import { Head, Link, useForm } from '@inertiajs/react'; -import { Button } from '@/Components/ui/button'; -import { Card, CardContent, CardHeader, CardTitle } from '@/Components/ui/card'; -import { Input } from '@/Components/ui/input'; -import { Label } from '@/Components/ui/label'; -import { Textarea } from '@/Components/ui/textarea'; -import { Badge } from '@/Components/ui/badge'; -import { ArrowLeft, Send, Upload, X, FileText, CalendarClock } from 'lucide-react'; -import { PageProps } from '@/types'; -import { FormEvent, useRef, useState } from 'react'; - -interface Criteria { id: number; name: string; weight: string; description?: string } -interface Invitation { - id: number; ulid: string; status: string; - package: { - id: number; ulid: string; title: string; description?: string; instructions?: string; - submission_deadline?: string; evaluation_mode: string; - project: { id: number; name: string; code: string } | null; - criteria: Criteria[]; - }; -} - -interface Props extends PageProps { - invitation: Invitation; -} - -export default function Submit({ invitation }: Props) { - const pkg = invitation.package; - const [files, setFiles] = useState([]); - const fileInputRef = useRef(null); - - const { data, setData, post, transform, processing, errors } = useForm({ - submitted_price: '', - completion_days: '', - validity_date: '', - technical_notes: '', - documents: [] as File[], - }); - - // Normalize empty date to null so Laravel's 'nullable|date' rule passes - transform((d) => ({ ...d, validity_date: d.validity_date || null })); - - const handleFileAdd = (e: React.ChangeEvent) => { - const selected = Array.from(e.target.files ?? []); - const updated = [...files, ...selected]; - setFiles(updated); - setData('documents', updated); - }; - - const removeFile = (i: number) => { - const updated = files.filter((_, idx) => idx !== i); - setFiles(updated); - setData('documents', updated); - }; - - const submit = (e: FormEvent) => { - e.preventDefault(); - post(route('bid-submissions.store', invitation.ulid), { - forceFormData: true, - }); - }; - - return ( - - -
-

Submit Bid

-

- {pkg.title}{pkg.project ? ` — ${pkg.project.name} (${pkg.project.code})` : ''} -

-
- - } - > - - -
-
- {/* Package Info */} - - Bid Package Details - -
- {pkg.submission_deadline && ( -
- - Deadline: {new Date(pkg.submission_deadline).toLocaleDateString()} -
- )} - {pkg.evaluation_mode === 'scored' ? 'Scored Evaluation' : 'Simple Selection'} -
- {pkg.description &&

{pkg.description}

} - {pkg.instructions && ( -
-

Instructions for Bidders

-

{pkg.instructions}

-
- )} - {pkg.evaluation_mode === 'scored' && pkg.criteria.length > 0 && ( -
-

Your submission will be scored on:

-
- {pkg.criteria.map(c => ( -
- {c.name} - {parseFloat(c.weight)}% -
- ))} -
-
- )} -
-
- - {/* Submission Form */} -
- - Your Proposal - -
-
- - setData('submitted_price', e.target.value)} - placeholder="0.00" - className={errors.submitted_price ? 'border-red-500' : ''} - /> - {errors.submitted_price &&

{errors.submitted_price}

} -
-
- - setData('completion_days', e.target.value)} - placeholder="e.g., 90" - className={errors.completion_days ? 'border-red-500' : ''} - /> - {errors.completion_days &&

{errors.completion_days}

} -
-
- -
- - setData('validity_date', e.target.value)} - className={errors.validity_date ? 'border-red-500' : ''} - /> - {errors.validity_date - ?

{errors.validity_date}

- :

The date until which your quoted price is valid.

- } -
- -
- -