chore(repo): normalize EOL and harden contact patch flow

- handle contact PATCH failures by checking model save result and returning HTTP 400 with the model error message
- update ContactDetailModel nested updates to enforce active-detail checks and use model update() with explicit failure propagation
- extend contact patch assertions and align test-create variants expectations to status=success for POST responses
- refresh composer lock metadata/dependency constraints and include generated docs/data/test files updated during normalization
- impact: API contract unchanged except clearer 400 error responses on invalid contact detail updates
This commit is contained in:
root 2026-04-17 05:38:11 +07:00
parent 7fd3dfddd8
commit 30c4e47304
99 changed files with 17585 additions and 17564 deletions

0
.codex Normal file
View File

4
.serena/.gitignore vendored
View File

@ -1,2 +1,2 @@
/cache
/project.local.yml
/cache
/project.local.yml

View File

@ -1,14 +1,14 @@
Essential commands for CLQMS development (run from repo root on Windows PowerShell):
`composer install` install PHP dependencies before running CodeIgniter or tests.
`npm install` sync `package-lock.json` for tooling such as API docs bundler.
`./vendor/bin/phpunit` run entire PHPUnit suite (or target files via `--filter`).
`php spark test --filter <Class>::<method>` focused test run when you know the class/method.
`php spark migrate` / `php spark migrate:rollback` apply or roll back database migrations.
`php spark serve` lightweight dev server for the API while developing locally.
`node public/bundle-api-docs.js` regenerate bundled OpenAPI docs whenever the YAML files change.
`git status`, `git diff`, `git log --oneline`, `git add <paths>`, `git commit`, `git pull`, `git push` version control workflow commands.
`ls` / `dir` / `Get-ChildItem` inspect directories in PowerShell; `cd` to move between directories.
`type <file>` or `Get-Content` view file contents when tools are not convenient.
Essential commands for CLQMS development (run from repo root on Windows PowerShell):
`composer install` install PHP dependencies before running CodeIgniter or tests.
`npm install` sync `package-lock.json` for tooling such as API docs bundler.
`./vendor/bin/phpunit` run entire PHPUnit suite (or target files via `--filter`).
`php spark test --filter <Class>::<method>` focused test run when you know the class/method.
`php spark migrate` / `php spark migrate:rollback` apply or roll back database migrations.
`php spark serve` lightweight dev server for the API while developing locally.
`node public/bundle-api-docs.js` regenerate bundled OpenAPI docs whenever the YAML files change.
`git status`, `git diff`, `git log --oneline`, `git add <paths>`, `git commit`, `git pull`, `git push` version control workflow commands.
`ls` / `dir` / `Get-ChildItem` inspect directories in PowerShell; `cd` to move between directories.
`type <file>` or `Get-Content` view file contents when tools are not convenient.
Use these commands routinely after code changes, tests, or migrations.

View File

@ -1,10 +1,10 @@
When a task is completed in CLQMS backend, follow these wrap-up steps:
1. Run relevant tests (`./vendor/bin/phpunit` or targeted `php spark test --filter ...`).
2. If migrations changed, run `php spark migrate` / `php spark migrate:rollback` locally and ensure schema updates succeed.
3. After editing OpenAPI documentation (YAML files or controller mappings), regenerate `public/api-docs.bundled.yaml` via `node public/bundle-api-docs.js` and check it into Git.
4. Confirm code adheres to PSR-12/CodeIgniter conventions (strict types, response format, transactions, guard clauses) before committing.
5. Review `git status/diff` to ensure only intended files are staged; do not commit `.env` or other secret files.
6. For shared logic changes, double-check lookup JSON cache use and response logging.
When a task is completed in CLQMS backend, follow these wrap-up steps:
1. Run relevant tests (`./vendor/bin/phpunit` or targeted `php spark test --filter ...`).
2. If migrations changed, run `php spark migrate` / `php spark migrate:rollback` locally and ensure schema updates succeed.
3. After editing OpenAPI documentation (YAML files or controller mappings), regenerate `public/api-docs.bundled.yaml` via `node public/bundle-api-docs.js` and check it into Git.
4. Confirm code adheres to PSR-12/CodeIgniter conventions (strict types, response format, transactions, guard clauses) before committing.
5. Review `git status/diff` to ensure only intended files are staged; do not commit `.env` or other secret files.
6. For shared logic changes, double-check lookup JSON cache use and response logging.
These steps keep the API consistent, documented, and tested before merging or deploying.

View File

@ -1,154 +1,154 @@
# the name by which the project can be referenced within Serena
project_name: "clqms01-be"
# list of languages for which language servers are started; choose from:
# al bash clojure cpp csharp
# csharp_omnisharp dart elixir elm erlang
# fortran fsharp go groovy haskell
# haxe java julia kotlin lua
# markdown
# matlab nix pascal perl php
# php_phpactor powershell python python_jedi r
# rego ruby ruby_solargraph rust scala
# swift terraform toml typescript typescript_vts
# vue yaml zig
# (This list may be outdated. For the current list, see values of Language enum here:
# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py
# For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.)
# Note:
# - For C, use cpp
# - For JavaScript, use typescript
# - For Free Pascal/Lazarus, use pascal
# Special requirements:
# Some languages require additional setup/installations.
# See here for details: https://oraios.github.io/serena/01-about/020_programming-languages.html#language-servers
# When using multiple languages, the first language server that supports a given file will be used for that file.
# The first language is the default language and the respective language server will be used as a fallback.
# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored.
languages:
- php
# the encoding used by text files in the project
# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings
encoding: "utf-8"
# line ending convention to use when writing source files.
# Possible values: unset (use global setting), "lf", "crlf", or "native" (platform default)
# This does not affect Serena's own files (e.g. memories and configuration files), which always use native line endings.
line_ending:
# The language backend to use for this project.
# If not set, the global setting from serena_config.yml is used.
# Valid values: LSP, JetBrains
# Note: the backend is fixed at startup. If a project with a different backend
# is activated post-init, an error will be returned.
language_backend:
# whether to use project's .gitignore files to ignore files
ignore_all_files_in_gitignore: true
# advanced configuration option allowing to configure language server-specific options.
# Maps the language key to the options.
# Have a look at the docstring of the constructors of the LS implementations within solidlsp (e.g., for C# or PHP) to see which options are available.
# No documentation on options means no options are available.
ls_specific_settings: {}
# list of additional paths to ignore in this project.
# Same syntax as gitignore, so you can use * and **.
# Note: global ignored_paths from serena_config.yml are also applied additively.
ignored_paths: []
# whether the project is in read-only mode
# If set to true, all editing tools will be disabled and attempts to use them will result in an error
# Added on 2025-04-18
read_only: false
# list of tool names to exclude.
# This extends the existing exclusions (e.g. from the global configuration)
#
# Below is the complete list of tools for convenience.
# To make sure you have the latest list of tools, and to view their descriptions,
# execute `uv run scripts/print_tool_overview.py`.
#
# * `activate_project`: Activates a project based on the project name or path.
# * `check_onboarding_performed`: Checks whether project onboarding was already performed.
# * `create_text_file`: Creates/overwrites a file in the project directory.
# * `delete_memory`: Delete a memory file. Should only happen if a user asks for it explicitly,
# for example by saying that the information retrieved from a memory file is no longer correct
# or no longer relevant for the project.
# * `edit_memory`: Replaces content matching a regular expression in a memory.
# * `execute_shell_command`: Executes a shell command.
# * `find_file`: Finds files in the given relative paths
# * `find_referencing_symbols`: Finds symbols that reference the given symbol using the language server backend
# * `find_symbol`: Performs a global (or local) search using the language server backend.
# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes.
# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file.
# * `initial_instructions`: Provides instructions Serena usage (i.e. the 'Serena Instructions Manual')
# for clients that do not read the initial instructions when the MCP server is connected.
# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol.
# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol.
# * `list_dir`: Lists files and directories in the given directory (optionally with recursion).
# * `list_memories`: List available memories. Any memory can be read using the `read_memory` tool.
# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building).
# * `read_file`: Reads a file within the project directory.
# * `read_memory`: Read the content of a memory file. This tool should only be used if the information
# is relevant to the current task. You can infer whether the information
# is relevant from the memory file name.
# You should not read the same memory file multiple times in the same conversation.
# * `rename_memory`: Renames or moves a memory. Moving between project and global scope is supported
# (e.g., renaming "global/foo" to "bar" moves it from global to project scope).
# * `rename_symbol`: Renames a symbol throughout the codebase using language server refactoring capabilities.
# For JB, we use a separate tool.
# * `replace_content`: Replaces content in a file (optionally using regular expressions).
# * `replace_symbol_body`: Replaces the full definition of a symbol using the language server backend.
# * `safe_delete_symbol`:
# * `search_for_pattern`: Performs a search for a pattern in the project.
# * `write_memory`: Write some information (utf-8-encoded) about this project that can be useful for future tasks to a memory in md format.
# The memory name should be meaningful.
excluded_tools: []
# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default).
# This extends the existing inclusions (e.g. from the global configuration).
included_optional_tools: []
# fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools.
# This cannot be combined with non-empty excluded_tools or included_optional_tools.
fixed_tools: []
# list of mode names to that are always to be included in the set of active modes
# The full set of modes to be activated is base_modes + default_modes.
# If the setting is undefined, the base_modes from the global configuration (serena_config.yml) apply.
# Otherwise, this setting overrides the global configuration.
# Set this to [] to disable base modes for this project.
# Set this to a list of mode names to always include the respective modes for this project.
base_modes:
# list of mode names that are to be activated by default.
# The full set of modes to be activated is base_modes + default_modes.
# If the setting is undefined, the default_modes from the global configuration (serena_config.yml) apply.
# Otherwise, this overrides the setting from the global configuration (serena_config.yml).
# This setting can, in turn, be overridden by CLI parameters (--mode).
default_modes:
# initial prompt for the project. It will always be given to the LLM upon activating the project
# (contrary to the memories, which are loaded on demand).
initial_prompt: ""
# time budget (seconds) per tool call for the retrieval of additional symbol information
# such as docstrings or parameter information.
# This overrides the corresponding setting in the global configuration; see the documentation there.
# If null or missing, use the setting from the global configuration.
symbol_info_budget:
# list of regex patterns which, when matched, mark a memory entry as readonly.
# Extends the list from the global configuration, merging the two lists.
read_only_memory_patterns: []
# list of regex patterns for memories to completely ignore.
# Matching memories will not appear in list_memories or activate_project output
# and cannot be accessed via read_memory or write_memory.
# To access ignored memory files, use the read_file tool on the raw file path.
# Extends the list from the global configuration, merging the two lists.
# Example: ["_archive/.*", "_episodes/.*"]
ignored_memory_patterns: []
# the name by which the project can be referenced within Serena
project_name: "clqms01-be"
# list of languages for which language servers are started; choose from:
# al bash clojure cpp csharp
# csharp_omnisharp dart elixir elm erlang
# fortran fsharp go groovy haskell
# haxe java julia kotlin lua
# markdown
# matlab nix pascal perl php
# php_phpactor powershell python python_jedi r
# rego ruby ruby_solargraph rust scala
# swift terraform toml typescript typescript_vts
# vue yaml zig
# (This list may be outdated. For the current list, see values of Language enum here:
# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py
# For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.)
# Note:
# - For C, use cpp
# - For JavaScript, use typescript
# - For Free Pascal/Lazarus, use pascal
# Special requirements:
# Some languages require additional setup/installations.
# See here for details: https://oraios.github.io/serena/01-about/020_programming-languages.html#language-servers
# When using multiple languages, the first language server that supports a given file will be used for that file.
# The first language is the default language and the respective language server will be used as a fallback.
# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored.
languages:
- php
# the encoding used by text files in the project
# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings
encoding: "utf-8"
# line ending convention to use when writing source files.
# Possible values: unset (use global setting), "lf", "crlf", or "native" (platform default)
# This does not affect Serena's own files (e.g. memories and configuration files), which always use native line endings.
line_ending:
# The language backend to use for this project.
# If not set, the global setting from serena_config.yml is used.
# Valid values: LSP, JetBrains
# Note: the backend is fixed at startup. If a project with a different backend
# is activated post-init, an error will be returned.
language_backend:
# whether to use project's .gitignore files to ignore files
ignore_all_files_in_gitignore: true
# advanced configuration option allowing to configure language server-specific options.
# Maps the language key to the options.
# Have a look at the docstring of the constructors of the LS implementations within solidlsp (e.g., for C# or PHP) to see which options are available.
# No documentation on options means no options are available.
ls_specific_settings: {}
# list of additional paths to ignore in this project.
# Same syntax as gitignore, so you can use * and **.
# Note: global ignored_paths from serena_config.yml are also applied additively.
ignored_paths: []
# whether the project is in read-only mode
# If set to true, all editing tools will be disabled and attempts to use them will result in an error
# Added on 2025-04-18
read_only: false
# list of tool names to exclude.
# This extends the existing exclusions (e.g. from the global configuration)
#
# Below is the complete list of tools for convenience.
# To make sure you have the latest list of tools, and to view their descriptions,
# execute `uv run scripts/print_tool_overview.py`.
#
# * `activate_project`: Activates a project based on the project name or path.
# * `check_onboarding_performed`: Checks whether project onboarding was already performed.
# * `create_text_file`: Creates/overwrites a file in the project directory.
# * `delete_memory`: Delete a memory file. Should only happen if a user asks for it explicitly,
# for example by saying that the information retrieved from a memory file is no longer correct
# or no longer relevant for the project.
# * `edit_memory`: Replaces content matching a regular expression in a memory.
# * `execute_shell_command`: Executes a shell command.
# * `find_file`: Finds files in the given relative paths
# * `find_referencing_symbols`: Finds symbols that reference the given symbol using the language server backend
# * `find_symbol`: Performs a global (or local) search using the language server backend.
# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes.
# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file.
# * `initial_instructions`: Provides instructions Serena usage (i.e. the 'Serena Instructions Manual')
# for clients that do not read the initial instructions when the MCP server is connected.
# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol.
# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol.
# * `list_dir`: Lists files and directories in the given directory (optionally with recursion).
# * `list_memories`: List available memories. Any memory can be read using the `read_memory` tool.
# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building).
# * `read_file`: Reads a file within the project directory.
# * `read_memory`: Read the content of a memory file. This tool should only be used if the information
# is relevant to the current task. You can infer whether the information
# is relevant from the memory file name.
# You should not read the same memory file multiple times in the same conversation.
# * `rename_memory`: Renames or moves a memory. Moving between project and global scope is supported
# (e.g., renaming "global/foo" to "bar" moves it from global to project scope).
# * `rename_symbol`: Renames a symbol throughout the codebase using language server refactoring capabilities.
# For JB, we use a separate tool.
# * `replace_content`: Replaces content in a file (optionally using regular expressions).
# * `replace_symbol_body`: Replaces the full definition of a symbol using the language server backend.
# * `safe_delete_symbol`:
# * `search_for_pattern`: Performs a search for a pattern in the project.
# * `write_memory`: Write some information (utf-8-encoded) about this project that can be useful for future tasks to a memory in md format.
# The memory name should be meaningful.
excluded_tools: []
# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default).
# This extends the existing inclusions (e.g. from the global configuration).
included_optional_tools: []
# fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools.
# This cannot be combined with non-empty excluded_tools or included_optional_tools.
fixed_tools: []
# list of mode names to that are always to be included in the set of active modes
# The full set of modes to be activated is base_modes + default_modes.
# If the setting is undefined, the base_modes from the global configuration (serena_config.yml) apply.
# Otherwise, this setting overrides the global configuration.
# Set this to [] to disable base modes for this project.
# Set this to a list of mode names to always include the respective modes for this project.
base_modes:
# list of mode names that are to be activated by default.
# The full set of modes to be activated is base_modes + default_modes.
# If the setting is undefined, the default_modes from the global configuration (serena_config.yml) apply.
# Otherwise, this overrides the setting from the global configuration (serena_config.yml).
# This setting can, in turn, be overridden by CLI parameters (--mode).
default_modes:
# initial prompt for the project. It will always be given to the LLM upon activating the project
# (contrary to the memories, which are loaded on demand).
initial_prompt: ""
# time budget (seconds) per tool call for the retrieval of additional symbol information
# such as docstrings or parameter information.
# This overrides the corresponding setting in the global configuration; see the documentation there.
# If null or missing, use the setting from the global configuration.
symbol_info_budget:
# list of regex patterns which, when matched, mark a memory entry as readonly.
# Extends the list from the global configuration, merging the two lists.
read_only_memory_patterns: []
# list of regex patterns for memories to completely ignore.
# Matching memories will not appear in list_memories or activate_project output
# and cannot be accessed via read_memory or write_memory.
# To access ignored memory files, use the read_file tool on the raw file path.
# Extends the list from the global configuration, merging the two lists.
# Example: ["_archive/.*", "_episodes/.*"]
ignored_memory_patterns: []

306
AGENTS.md
View File

@ -1,153 +1,153 @@
# AGENTS.md - Code Guidelines for CLQMS
> **CLQMS (Clinical Laboratory Quality Management System)** headless REST API backend built on CodeIgniter 4 with a focus on laboratory workflows, JWT authentication, and synchronized OpenAPI documentation.
---
## Repository Snapshot
- `app/` holds controllers, models, filters, and traits wired through PSR-4 `App\` namespace.
- `tests/` relies on CodeIgniter's testing helpers plus Faker for deterministic fixtures.
- Shared response helpers and ValueSet lookups live under `app/Libraries` and `app/Traits` and should be reused before introducing new helpers.
- Environment values, secrets, and database credentials live in `.env` but are never committed; treat the file as a reference for defaults.
---
## Build, Lint & Test
All commands run from the repository root.
```bash
# Run the entire PHPUnit suite
./vendor/bin/phpunit
# Target a single test file (fast verification)
./vendor/bin/phpunit tests/feature/Patients/PatientCreateTest.php
# Run one test case by method
./vendor/bin/phpunit --filter testCreatePatientSuccess tests/feature/Patients/PatientCreateTest.php
# Generate scaffolding (model, controller, migration)
php spark make:model <Name>
php spark make:controller <Name>
php spark make:migration <name>
# Database migrations
php spark migrate
php spark migrate:rollback
# After OpenAPI edits
node public/bundle-api-docs.js
```
Use `php spark test --filter <Class>::<method>` when filtering more than one test file is cumbersome.
---
## Agent Rules Scan
- No `.cursor/rules/*` or `.cursorrules` directory detected; continue without Cursor-specific constraints.
- No `.github/copilot-instructions.md` present; Copilot behaviors revert to general GitHub defaults.
---
## Coding Standards
### Language & Formatting
- PHP 8.1+ is the baseline; enable `declare(strict_types=1)` at the top of new files when practical.
- Follow PSR-12 for spacing, line length (~120), and brace placement; prefer 4 spaces and avoid tabs.
- Use short arrays `[]`, and wrap multiline arguments/arrays with one-per-line items.
- Favor expression statements that return early (guard clauses) and keep nested logic shallow.
- Keep methods under ~40 lines when possible; extract private helpers for repeated flows.
### Naming & Types
- Classes, controllers, libraries, and traits: PascalCase (e.g., `PatientImportController`).
- Methods, services, traits: camelCase (`fetchActivePatients`).
- Properties: camelCase for new code; legacy snake_case may persist but avoid new snake_case unless mirroring legacy columns.
- Constants: UPPER_SNAKE_CASE.
- DTOs/array shapes: Use descriptive names (`$patientInput`, `$validatedPayload`).
- Type hints required for method arguments/returns; use union/nullables (e.g., `?string`) instead of doc-only comments.
- Prefer PHPDoc only when type inference fails (complex union or array shapes) but still keep method summaries concise.
### Imports & Structure
- Namespace declarations at the very top followed by grouped `use` statements.
- Import order: Core framework (`CodeIgniter`), then `App\`, then third-party packages (Firebase, Faker, etc.). Keep each group alphabetical.
- No inline `use` statements inside methods.
- Keep `use` statements de-duplicated; rely on IDE or `phpcbf` to reorder.
### Controller Structure
- Controllers orchestrate request validation, delegates to services/models, and return `ResponseTrait` responses; avoid direct DB queries here.
- Inject models/services via constructor when they are reused. When instantiating on the fly, reference FQCN (`new \App\Models\...`).
- Map HTTP verbs to semantic methods (`index`, `show`, `create`, `update`, `delete`). Keep action methods under 30 lines by delegating heavy lifting to models or libraries.
- Always respond through `$this->respond()` or `$this->respondCreated()` so JSON structure stays consistent.
### Response & Error Handling
- All responses follow `{ status, message, data }`. `status` values: `success`, `failed`, or `error`.
- Use `$this->respondCreated()`, `$this->respondNoContent()`, or `$this->respond()` with explicit HTTP codes.
- Wrap JWT/external calls in try/catch. Log unexpected exceptions with `log_message('error', $e->getMessage())` before responding with a sanitized failure.
- For validation failures, return HTTP 400 with detailed message; unauthorized access returns 401. Maintain parity with existing tests.
### Database & Transactions
- Use Query Builder or Model methods; enable `use App\Models\BaseModel` which handles UTC conversions.
- Always call `helper('utc')` when manipulating timestamps.
- Wrap multi-table changes in `$this->db->transStart()` / `$this->db->transComplete()` and check `transStatus()` to abort if false.
- Run `checkDbError()` (existing helper) after saves when manual queries are necessary.
### Service Helpers & Libraries
- Encapsulate complex lookups (ValueSet, encryption) inside `app/Libraries` or Traits.
- Reuse `App\Libraries\Lookups` for consistent label/value translations.
- Keep shared logic (e.g., response formatting, JWT decoding) inside Traits and import them via `use`.
### Testing & Coverage
- Place feature tests under `tests/Feature`, unit tests under `tests/Unit`.
- Test class names should follow `ClassNameTest`; methods follow `test<Action><Scenario><Result>` (e.g., `testCreatePatientValidationFail`).
- Use `FeatureTestTrait` and `CIUnitTestCase` for API tests; prefer `withBodyFormat('json')->post()` flows.
- Assert status codes: 200 for GET/PATCH, 201 for POST, 400 for validation, 401 for auth, 404 for missing resources, 500 for server errors.
- Run targeted tests during development, full suite before merging.
### Documentation & API Sync
- Whenever a controller or route changes, update `public/paths/<resource>.yaml` and matching `public/components/schemas`. Add tags or schema refs in `public/api-docs.yaml`.
- After editing OpenAPI files, regenerate the bundled docs with `node public/bundle-api-docs.js`. Check `public/api-docs.bundled.yaml` into version control.
- Keep the controller-to-YAML mapping table updated to reflect new resources.
### Routing Conventions
- Keep route definitions grouped inside `$routes->group('api/<resource>')` blocks in `app/Config/Routes.php`.
- Prefer nested controllers (e.g., `Patient\PatientController`) for domain partitioning.
- Use RESTful verbs (GET: index/show, POST: create, PATCH: update, DELETE: delete) to keep behavior predictable.
- Document side effects (snapshots, audit logs) directly in the corresponding OpenAPI `paths` file.
### Environment & Secrets
- Use `.env` as the source of truth for database/jwt settings. Do not commit production credentials.
- Sample values are provided in `.env`; copy to `.env.local` or CI secrets with overrides.
- `JWT_SECRET` must be treated as sensitive and rotated via environment updates only.
### Workflows & Misc
- Use `php spark migrate`/`migrate:rollback` for schema changes.
- For seeding or test fixtures, prefer factories (Faker) seeded in `tests/Support` when available.
- Document major changes in `issues.md` or dedicated feature docs under `docs/` before merging.
### Security & Filters
- Apply the `auth` filter to every protected route, and keep `ApiKey` or other custom filters consolidated under `app/Filters`.
- Sanitize user inputs via `filter_var`, `esc()` helpers, or validated entities before they hit the database.
- Always use parameterized queries/Model `save()` methods to prevent SQL injection, especially with legacy PascalCase columns.
- Respond 401 for missing tokens, 403 when permissions fail, and log sanitized details for ops debugging.
### Legacy Field Naming & ValueSets
- Databases use PascalCase columns such as `PatientID`, `NameFirst`, `CreatedAt`. Keep migration checks aware of these names.
- ValueSet lookups centralize label translation: `Lookups::get('gender')`, `Lookups::getLabel('gender', '1')`, `Lookups::transformLabels($payload, ['Sex' => 'gender'])`.
- Prefer `App\Libraries\Lookups` or `app/Traits/ValueSetTrait` to avoid ad-hoc mappings.
### Nested Data Handling
- For entities that carry related collections (`PatIdt`, `PatCom`, `PatAtt`), extract nested arrays before filtering and validating.
- Use transactions whenever multi-table inserts/updates occur so orphan rows are avoided.
- Guard against empty/null arrays by normalizing to `[]` before iterating.
### Observability & Logging
- Use `log_message('info', ...)` for happy-path checkpoints and `'error'` for catch-all failures.
- Avoid leaking sensitive values (tokens, secrets) in logs; log IDs or hash digests instead.
- Keep `writable/logs` clean by rotating or pruning stale log files with automation outside the repo.
---
## Final Notes for Agents
- This repo has no UI layer; focus exclusively on REST interactions.
- Always pull `public/api-docs.bundled.yaml` in after running `node public/bundle-api-docs.js` so downstream services see the latest contract.
- When in doubt, align with existing controller traits and response helpers to avoid duplicating logic.
- Rely on Serena tools for guided edits, searches, and context summaries (use the available symbolic and search tools before running shell commands).
# AGENTS.md - Code Guidelines for CLQMS
> **CLQMS (Clinical Laboratory Quality Management System)** headless REST API backend built on CodeIgniter 4 with a focus on laboratory workflows, JWT authentication, and synchronized OpenAPI documentation.
---
## Repository Snapshot
- `app/` holds controllers, models, filters, and traits wired through PSR-4 `App\` namespace.
- `tests/` relies on CodeIgniter's testing helpers plus Faker for deterministic fixtures.
- Shared response helpers and ValueSet lookups live under `app/Libraries` and `app/Traits` and should be reused before introducing new helpers.
- Environment values, secrets, and database credentials live in `.env` but are never committed; treat the file as a reference for defaults.
---
## Build, Lint & Test
All commands run from the repository root.
```bash
# Run the entire PHPUnit suite
./vendor/bin/phpunit
# Target a single test file (fast verification)
./vendor/bin/phpunit tests/feature/Patients/PatientCreateTest.php
# Run one test case by method
./vendor/bin/phpunit --filter testCreatePatientSuccess tests/feature/Patients/PatientCreateTest.php
# Generate scaffolding (model, controller, migration)
php spark make:model <Name>
php spark make:controller <Name>
php spark make:migration <name>
# Database migrations
php spark migrate
php spark migrate:rollback
# After OpenAPI edits
node public/bundle-api-docs.js
```
Use `php spark test --filter <Class>::<method>` when filtering more than one test file is cumbersome.
---
## Agent Rules Scan
- No `.cursor/rules/*` or `.cursorrules` directory detected; continue without Cursor-specific constraints.
- No `.github/copilot-instructions.md` present; Copilot behaviors revert to general GitHub defaults.
---
## Coding Standards
### Language & Formatting
- PHP 8.1+ is the baseline; enable `declare(strict_types=1)` at the top of new files when practical.
- Follow PSR-12 for spacing, line length (~120), and brace placement; prefer 4 spaces and avoid tabs.
- Use short arrays `[]`, and wrap multiline arguments/arrays with one-per-line items.
- Favor expression statements that return early (guard clauses) and keep nested logic shallow.
- Keep methods under ~40 lines when possible; extract private helpers for repeated flows.
### Naming & Types
- Classes, controllers, libraries, and traits: PascalCase (e.g., `PatientImportController`).
- Methods, services, traits: camelCase (`fetchActivePatients`).
- Properties: camelCase for new code; legacy snake_case may persist but avoid new snake_case unless mirroring legacy columns.
- Constants: UPPER_SNAKE_CASE.
- DTOs/array shapes: Use descriptive names (`$patientInput`, `$validatedPayload`).
- Type hints required for method arguments/returns; use union/nullables (e.g., `?string`) instead of doc-only comments.
- Prefer PHPDoc only when type inference fails (complex union or array shapes) but still keep method summaries concise.
### Imports & Structure
- Namespace declarations at the very top followed by grouped `use` statements.
- Import order: Core framework (`CodeIgniter`), then `App\`, then third-party packages (Firebase, Faker, etc.). Keep each group alphabetical.
- No inline `use` statements inside methods.
- Keep `use` statements de-duplicated; rely on IDE or `phpcbf` to reorder.
### Controller Structure
- Controllers orchestrate request validation, delegates to services/models, and return `ResponseTrait` responses; avoid direct DB queries here.
- Inject models/services via constructor when they are reused. When instantiating on the fly, reference FQCN (`new \App\Models\...`).
- Map HTTP verbs to semantic methods (`index`, `show`, `create`, `update`, `delete`). Keep action methods under 30 lines by delegating heavy lifting to models or libraries.
- Always respond through `$this->respond()` or `$this->respondCreated()` so JSON structure stays consistent.
### Response & Error Handling
- All responses follow `{ status, message, data }`. `status` values: `success`, `failed`, or `error`.
- Use `$this->respondCreated()`, `$this->respondNoContent()`, or `$this->respond()` with explicit HTTP codes.
- Wrap JWT/external calls in try/catch. Log unexpected exceptions with `log_message('error', $e->getMessage())` before responding with a sanitized failure.
- For validation failures, return HTTP 400 with detailed message; unauthorized access returns 401. Maintain parity with existing tests.
### Database & Transactions
- Use Query Builder or Model methods; enable `use App\Models\BaseModel` which handles UTC conversions.
- Always call `helper('utc')` when manipulating timestamps.
- Wrap multi-table changes in `$this->db->transStart()` / `$this->db->transComplete()` and check `transStatus()` to abort if false.
- Run `checkDbError()` (existing helper) after saves when manual queries are necessary.
### Service Helpers & Libraries
- Encapsulate complex lookups (ValueSet, encryption) inside `app/Libraries` or Traits.
- Reuse `App\Libraries\Lookups` for consistent label/value translations.
- Keep shared logic (e.g., response formatting, JWT decoding) inside Traits and import them via `use`.
### Testing & Coverage
- Place feature tests under `tests/Feature`, unit tests under `tests/Unit`.
- Test class names should follow `ClassNameTest`; methods follow `test<Action><Scenario><Result>` (e.g., `testCreatePatientValidationFail`).
- Use `FeatureTestTrait` and `CIUnitTestCase` for API tests; prefer `withBodyFormat('json')->post()` flows.
- Assert status codes: 200 for GET/PATCH, 201 for POST, 400 for validation, 401 for auth, 404 for missing resources, 500 for server errors.
- Run targeted tests during development, full suite before merging.
### Documentation & API Sync
- Whenever a controller or route changes, update `public/paths/<resource>.yaml` and matching `public/components/schemas`. Add tags or schema refs in `public/api-docs.yaml`.
- After editing OpenAPI files, regenerate the bundled docs with `node public/bundle-api-docs.js`. Check `public/api-docs.bundled.yaml` into version control.
- Keep the controller-to-YAML mapping table updated to reflect new resources.
### Routing Conventions
- Keep route definitions grouped inside `$routes->group('api/<resource>')` blocks in `app/Config/Routes.php`.
- Prefer nested controllers (e.g., `Patient\PatientController`) for domain partitioning.
- Use RESTful verbs (GET: index/show, POST: create, PATCH: update, DELETE: delete) to keep behavior predictable.
- Document side effects (snapshots, audit logs) directly in the corresponding OpenAPI `paths` file.
### Environment & Secrets
- Use `.env` as the source of truth for database/jwt settings. Do not commit production credentials.
- Sample values are provided in `.env`; copy to `.env.local` or CI secrets with overrides.
- `JWT_SECRET` must be treated as sensitive and rotated via environment updates only.
### Workflows & Misc
- Use `php spark migrate`/`migrate:rollback` for schema changes.
- For seeding or test fixtures, prefer factories (Faker) seeded in `tests/Support` when available.
- Document major changes in `issues.md` or dedicated feature docs under `docs/` before merging.
### Security & Filters
- Apply the `auth` filter to every protected route, and keep `ApiKey` or other custom filters consolidated under `app/Filters`.
- Sanitize user inputs via `filter_var`, `esc()` helpers, or validated entities before they hit the database.
- Always use parameterized queries/Model `save()` methods to prevent SQL injection, especially with legacy PascalCase columns.
- Respond 401 for missing tokens, 403 when permissions fail, and log sanitized details for ops debugging.
### Legacy Field Naming & ValueSets
- Databases use PascalCase columns such as `PatientID`, `NameFirst`, `CreatedAt`. Keep migration checks aware of these names.
- ValueSet lookups centralize label translation: `Lookups::get('gender')`, `Lookups::getLabel('gender', '1')`, `Lookups::transformLabels($payload, ['Sex' => 'gender'])`.
- Prefer `App\Libraries\Lookups` or `app/Traits/ValueSetTrait` to avoid ad-hoc mappings.
### Nested Data Handling
- For entities that carry related collections (`PatIdt`, `PatCom`, `PatAtt`), extract nested arrays before filtering and validating.
- Use transactions whenever multi-table inserts/updates occur so orphan rows are avoided.
- Guard against empty/null arrays by normalizing to `[]` before iterating.
### Observability & Logging
- Use `log_message('info', ...)` for happy-path checkpoints and `'error'` for catch-all failures.
- Avoid leaking sensitive values (tokens, secrets) in logs; log IDs or hash digests instead.
- Keep `writable/logs` clean by rotating or pruning stale log files with automation outside the repo.
---
## Final Notes for Agents
- This repo has no UI layer; focus exclusively on REST interactions.
- Always pull `public/api-docs.bundled.yaml` in after running `node public/bundle-api-docs.js` so downstream services see the latest contract.
- When in doubt, align with existing controller traits and response helpers to avoid duplicating logic.
- Rely on Serena tools for guided edits, searches, and context summaries (use the available symbolic and search tools before running shell commands).

View File

@ -1,60 +1,60 @@
<?php
namespace App\Controllers\Audit;
use App\Controllers\BaseController;
use App\Services\AuditLogService;
use App\Traits\ResponseTrait;
use CodeIgniter\HTTP\ResponseInterface;
use InvalidArgumentException;
class AuditLogController extends BaseController
{
use ResponseTrait;
private AuditLogService $auditLogService;
public function __construct()
{
$this->auditLogService = new AuditLogService();
}
public function index(): ResponseInterface
{
$filters = [
'table' => $this->request->getGet('table'),
'rec_id' => $this->request->getGet('rec_id') ?? $this->request->getGet('recId'),
'event_id' => $this->request->getGet('event_id') ?? $this->request->getGet('eventId'),
'activity_id' => $this->request->getGet('activity_id') ?? $this->request->getGet('activityId'),
'from' => $this->request->getGet('from'),
'to' => $this->request->getGet('to'),
'search' => $this->request->getGet('search'),
'page' => $this->request->getGet('page'),
'perPage' => $this->request->getGet('perPage') ?? $this->request->getGet('per_page'),
];
try {
$payload = $this->auditLogService->fetchLogs($filters);
return $this->respond([
'status' => 'success',
'message' => 'Audit logs retrieved successfully',
'data' => $payload,
], 200);
} catch (InvalidArgumentException $e) {
return $this->respond([
'status' => 'failed',
'message' => $e->getMessage(),
'data' => null,
], 400);
} catch (\Throwable $e) {
log_message('error', 'AuditLogController::index error: ' . $e->getMessage());
return $this->respond([
'status' => 'failed',
'message' => 'Unable to retrieve audit logs',
'data' => null,
], 500);
}
}
}
<?php
namespace App\Controllers\Audit;
use App\Controllers\BaseController;
use App\Services\AuditLogService;
use App\Traits\ResponseTrait;
use CodeIgniter\HTTP\ResponseInterface;
use InvalidArgumentException;
class AuditLogController extends BaseController
{
use ResponseTrait;
private AuditLogService $auditLogService;
public function __construct()
{
$this->auditLogService = new AuditLogService();
}
public function index(): ResponseInterface
{
$filters = [
'table' => $this->request->getGet('table'),
'rec_id' => $this->request->getGet('rec_id') ?? $this->request->getGet('recId'),
'event_id' => $this->request->getGet('event_id') ?? $this->request->getGet('eventId'),
'activity_id' => $this->request->getGet('activity_id') ?? $this->request->getGet('activityId'),
'from' => $this->request->getGet('from'),
'to' => $this->request->getGet('to'),
'search' => $this->request->getGet('search'),
'page' => $this->request->getGet('page'),
'perPage' => $this->request->getGet('perPage') ?? $this->request->getGet('per_page'),
];
try {
$payload = $this->auditLogService->fetchLogs($filters);
return $this->respond([
'status' => 'success',
'message' => 'Audit logs retrieved successfully',
'data' => $payload,
], 200);
} catch (InvalidArgumentException $e) {
return $this->respond([
'status' => 'failed',
'message' => $e->getMessage(),
'data' => null,
], 400);
} catch (\Throwable $e) {
log_message('error', 'AuditLogController::index error: ' . $e->getMessage());
return $this->respond([
'status' => 'failed',
'message' => 'Unable to retrieve audit logs',
'data' => null,
], 500);
}
}
}

View File

@ -1,73 +1,73 @@
<?php
namespace App\Controllers\Contact;
<?php
namespace App\Controllers\Contact;
use App\Traits\PatchValidationTrait;
use App\Traits\ResponseTrait;
use App\Controllers\BaseController;
use App\Libraries\ValueSet;
use App\Models\Contact\ContactModel;
class ContactController extends BaseController {
use ResponseTrait;
use PatchValidationTrait;
protected $db;
protected $model;
protected $rules;
protected $patchRules;
public function __construct() {
$this->db = \Config\Database::connect();
$this->model = new ContactModel();
$this->rules = [ 'NameFirst' => 'required' ];
$this->patchRules = [ 'NameFirst' => 'permit_empty' ];
}
public function index() {
$ContactName = $this->request->getVar('ContactName');
$Specialty = $this->request->getVar('Specialty');
$rows = $this->model->getContacts($ContactName, $Specialty);
if (empty($rows)) {
return $this->respond([ 'status' => 'success', 'message' => "no Data.", 'data' => [] ], 200);
}
$rows = ValueSet::transformLabels($rows, [
'Specialty' => 'specialty',
'Occupation' => 'occupation',
]);
return $this->respond([ 'status' => 'success', 'message'=> "fetch success", 'data' => $rows ], 200);
}
public function show($ContactID = null) {
$model = new ContactModel();
$row = $model->getContactWithDetail($ContactID);
if (empty($row)) {
return $this->respond([ 'status' => 'success', 'message' => "no Data.", 'data' => null ], 200);
}
$row = ValueSet::transformLabels([$row], [
'Specialty' => 'specialty',
'Occupation' => 'occupation',
])[0];
return $this->respond([ 'status' => 'success', 'message'=> "fetch success", 'data' => $row ], 200);
}
public function delete() {
try {
$input = $this->request->getJSON(true);
$ContactID = $input["ContactID"];
if (!$ContactID) { return $this->failValidationErrors('ContactID is required.'); }
$this->model->delete($ContactID);
return $this->respondDeleted([ 'status' => 'success', 'message' => "Contact with {$ContactID} deleted successfully."]);
} catch (\Throwable $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
}
}
public function index() {
$ContactName = $this->request->getVar('ContactName');
$Specialty = $this->request->getVar('Specialty');
$rows = $this->model->getContacts($ContactName, $Specialty);
if (empty($rows)) {
return $this->respond([ 'status' => 'success', 'message' => "no Data.", 'data' => [] ], 200);
}
$rows = ValueSet::transformLabels($rows, [
'Specialty' => 'specialty',
'Occupation' => 'occupation',
]);
return $this->respond([ 'status' => 'success', 'message'=> "fetch success", 'data' => $rows ], 200);
}
public function show($ContactID = null) {
$model = new ContactModel();
$row = $model->getContactWithDetail($ContactID);
if (empty($row)) {
return $this->respond([ 'status' => 'success', 'message' => "no Data.", 'data' => null ], 200);
}
$row = ValueSet::transformLabels([$row], [
'Specialty' => 'specialty',
'Occupation' => 'occupation',
])[0];
return $this->respond([ 'status' => 'success', 'message'=> "fetch success", 'data' => $row ], 200);
}
public function delete() {
try {
$input = $this->request->getJSON(true);
$ContactID = $input["ContactID"];
if (!$ContactID) { return $this->failValidationErrors('ContactID is required.'); }
$this->model->delete($ContactID);
return $this->respondDeleted([ 'status' => 'success', 'message' => "Contact with {$ContactID} deleted successfully."]);
} catch (\Throwable $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
}
}
public function create() {
$input = $this->request->getJSON(true);
if (!$this->validateData($input, $this->rules)) { return $this->failValidationErrors($this->validator->getErrors()); }
@ -87,7 +87,7 @@ class ContactController extends BaseController {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
}
}
public function update($ContactID = null) {
$input = $this->requirePatchPayload($this->request->getJSON(true));
if ($input === null) {
@ -115,9 +115,17 @@ class ContactController extends BaseController {
$input['ContactID'] = $id;
try {
$this->model->saveContact($input);
$id = $input['ContactID'];
return $this->respond([ 'status' => 'success', 'message' => 'data updated successfully', 'data' => $id ], 200);
$result = $this->model->saveContact($input);
if (($result['status'] ?? 'error') !== 'success') {
return $this->respond([
'status' => 'failed',
'message' => $result['message'] ?? 'Failed to update contact',
'data' => []
], 400);
}
return $this->respond([ 'status' => 'success', 'message' => 'data updated successfully', 'data' => $result ], 200);
} catch (\Throwable $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
}

View File

@ -1,112 +1,112 @@
<?php
namespace App\Controllers\Organization;
use App\Traits\PatchValidationTrait;
use App\Traits\ResponseTrait;
use App\Controllers\BaseController;
use App\Models\Organization\CodingSysModel;
class CodingSysController extends BaseController {
use ResponseTrait;
use PatchValidationTrait;
protected $db;
protected $model;
public function __construct() {
$this->db = \Config\Database::connect();
$this->model = new CodingSysModel();
}
public function index() {
$filter = [
'CodingSysAbb' => $this->request->getVar('CodingSysAbb'),
'FullText' => $this->request->getVar('FullText'),
];
$builder = $this->model;
if (!empty($filter['CodingSysAbb'])) {
$builder->like('CodingSysAbb', $filter['CodingSysAbb'], 'both');
}
if (!empty($filter['FullText'])) {
$builder->like('FullText', $filter['FullText'], 'both');
}
$rows = $builder->findAll();
if (empty($rows)) {
return $this->respond(['status' => 'success', 'message' => 'no Data.', 'data' => []], 200);
}
return $this->respond(['status' => 'success', 'message' => 'fetch success', 'data' => $rows], 200);
}
public function show($CodingSysID = null) {
$row = $this->model->where('CodingSysID', $CodingSysID)->first();
if (empty($row)) {
return $this->respond(['status' => 'success', 'message' => 'no Data.', 'data' => null], 200);
}
return $this->respond(['status' => 'success', 'message' => 'fetch success', 'data' => $row], 200);
}
public function delete() {
try {
$input = $this->request->getJSON(true);
$id = $input['CodingSysID'] ?? null;
if (!$id) {
return $this->failValidationErrors('CodingSysID is required.');
}
$this->model->delete($id);
return $this->respondDeleted(['status' => 'success', 'message' => "{$id} deleted successfully."]);
} catch (\Throwable $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
}
}
public function create() {
$input = $this->request->getJSON(true);
try {
$id = $this->model->insert($input, true);
return $this->respondCreated(['status' => 'success', 'message' => 'data created successfully', 'data' => $id], 201);
} catch (\Throwable $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
}
}
public function update($CodingSysID = null) {
$input = $this->requirePatchPayload($this->request->getJSON(true));
if ($input === null) {
return;
}
$id = $this->requirePatchId($CodingSysID, 'CodingSysID');
if ($id === null) {
return;
}
$existing = $this->model->find($id);
if (!$existing) {
return $this->respond(['status' => 'failed', 'message' => 'CodingSys not found', 'data' => []], 404);
}
if (isset($input['CodingSysID']) && (string) $input['CodingSysID'] !== (string) $id) {
return $this->failValidationErrors('CodingSysID in URL does not match body.');
}
$input['CodingSysID'] = $id;
try {
$this->model->update($id, $input);
return $this->respondCreated(['status' => 'success', 'message' => 'data updated successfully', 'data' => $id], 201);
} catch (\Throwable $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
}
}
}
<?php
namespace App\Controllers\Organization;
use App\Traits\PatchValidationTrait;
use App\Traits\ResponseTrait;
use App\Controllers\BaseController;
use App\Models\Organization\CodingSysModel;
class CodingSysController extends BaseController {
use ResponseTrait;
use PatchValidationTrait;
protected $db;
protected $model;
public function __construct() {
$this->db = \Config\Database::connect();
$this->model = new CodingSysModel();
}
public function index() {
$filter = [
'CodingSysAbb' => $this->request->getVar('CodingSysAbb'),
'FullText' => $this->request->getVar('FullText'),
];
$builder = $this->model;
if (!empty($filter['CodingSysAbb'])) {
$builder->like('CodingSysAbb', $filter['CodingSysAbb'], 'both');
}
if (!empty($filter['FullText'])) {
$builder->like('FullText', $filter['FullText'], 'both');
}
$rows = $builder->findAll();
if (empty($rows)) {
return $this->respond(['status' => 'success', 'message' => 'no Data.', 'data' => []], 200);
}
return $this->respond(['status' => 'success', 'message' => 'fetch success', 'data' => $rows], 200);
}
public function show($CodingSysID = null) {
$row = $this->model->where('CodingSysID', $CodingSysID)->first();
if (empty($row)) {
return $this->respond(['status' => 'success', 'message' => 'no Data.', 'data' => null], 200);
}
return $this->respond(['status' => 'success', 'message' => 'fetch success', 'data' => $row], 200);
}
public function delete() {
try {
$input = $this->request->getJSON(true);
$id = $input['CodingSysID'] ?? null;
if (!$id) {
return $this->failValidationErrors('CodingSysID is required.');
}
$this->model->delete($id);
return $this->respondDeleted(['status' => 'success', 'message' => "{$id} deleted successfully."]);
} catch (\Throwable $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
}
}
public function create() {
$input = $this->request->getJSON(true);
try {
$id = $this->model->insert($input, true);
return $this->respondCreated(['status' => 'success', 'message' => 'data created successfully', 'data' => $id], 201);
} catch (\Throwable $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
}
}
public function update($CodingSysID = null) {
$input = $this->requirePatchPayload($this->request->getJSON(true));
if ($input === null) {
return;
}
$id = $this->requirePatchId($CodingSysID, 'CodingSysID');
if ($id === null) {
return;
}
$existing = $this->model->find($id);
if (!$existing) {
return $this->respond(['status' => 'failed', 'message' => 'CodingSys not found', 'data' => []], 404);
}
if (isset($input['CodingSysID']) && (string) $input['CodingSysID'] !== (string) $id) {
return $this->failValidationErrors('CodingSysID in URL does not match body.');
}
$input['CodingSysID'] = $id;
try {
$this->model->update($id, $input);
return $this->respondCreated(['status' => 'success', 'message' => 'data updated successfully', 'data' => $id], 201);
} catch (\Throwable $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
}
}
}

View File

@ -1,124 +1,124 @@
<?php
namespace App\Controllers\Organization;
use App\Traits\PatchValidationTrait;
use App\Traits\ResponseTrait;
use App\Controllers\BaseController;
use App\Models\Organization\HostAppModel;
class HostAppController extends BaseController {
use ResponseTrait;
use PatchValidationTrait;
protected $db;
protected $model;
public function __construct() {
$this->db = \Config\Database::connect();
$this->model = new HostAppModel();
}
public function index() {
$filter = [
'HostAppID' => $this->request->getVar('HostAppID'),
'HostAppName' => $this->request->getVar('HostAppName'),
];
$builder = $this->model->select('hostapp.*, site.SiteName')
->join('site', 'site.SiteID = hostapp.SiteID', 'left');
if (!empty($filter['HostAppID'])) {
if (!ctype_digit((string) $filter['HostAppID'])) {
return $this->failValidationErrors('HostAppID filter must be a valid integer.');
}
$builder->where('hostapp.HostAppID', (int) $filter['HostAppID']);
}
if (!empty($filter['HostAppName'])) {
$builder->like('hostapp.HostAppName', $filter['HostAppName'], 'both');
}
$rows = $builder->findAll();
if (empty($rows)) {
return $this->respond(['status' => 'success', 'message' => 'no Data.', 'data' => []], 200);
}
return $this->respond(['status' => 'success', 'message' => 'fetch success', 'data' => $rows], 200);
}
public function show($HostAppID = null) {
$id = $this->requirePatchId($HostAppID, 'HostAppID');
if ($id === null) {
return;
}
$row = $this->model->select('hostapp.*, site.SiteName')
->join('site', 'site.SiteID = hostapp.SiteID', 'left')
->where('hostapp.HostAppID', $id)
->first();
if (empty($row)) {
return $this->respond(['status' => 'success', 'message' => 'no Data.', 'data' => null], 200);
}
return $this->respond(['status' => 'success', 'message' => 'fetch success', 'data' => $row], 200);
}
public function delete() {
try {
$input = $this->request->getJSON(true);
$id = $this->requirePatchId($input['HostAppID'] ?? null, 'HostAppID');
if ($id === null) {
return;
}
$this->model->delete($id);
return $this->respondDeleted(['status' => 'success', 'message' => "{$id} deleted successfully."]);
} catch (\Throwable $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
}
}
public function create() {
$input = $this->request->getJSON(true);
try {
unset($input['HostAppID']);
$id = $this->model->insert($input, true);
return $this->respondCreated(['status' => 'success', 'message' => 'data created successfully', 'data' => $id], 201);
} catch (\Throwable $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
}
}
public function update($HostAppID = null) {
$input = $this->requirePatchPayload($this->request->getJSON(true));
if ($input === null) {
return;
}
$id = $this->requirePatchId($HostAppID, 'HostAppID');
if ($id === null) {
return;
}
$existing = $this->model->find($id);
if (!$existing) {
return $this->respond(['status' => 'failed', 'message' => 'HostApp not found', 'data' => []], 404);
}
if (isset($input['HostAppID'])) {
if ((string) $input['HostAppID'] !== (string) $id) {
return $this->failValidationErrors('HostAppID in URL does not match body.');
}
unset($input['HostAppID']);
}
try {
$this->model->update($id, $input);
return $this->respond(['status' => 'success', 'message' => 'data updated successfully', 'data' => $id], 200);
} catch (\Throwable $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
}
}
}
<?php
namespace App\Controllers\Organization;
use App\Traits\PatchValidationTrait;
use App\Traits\ResponseTrait;
use App\Controllers\BaseController;
use App\Models\Organization\HostAppModel;
class HostAppController extends BaseController {
use ResponseTrait;
use PatchValidationTrait;
protected $db;
protected $model;
public function __construct() {
$this->db = \Config\Database::connect();
$this->model = new HostAppModel();
}
public function index() {
$filter = [
'HostAppID' => $this->request->getVar('HostAppID'),
'HostAppName' => $this->request->getVar('HostAppName'),
];
$builder = $this->model->select('hostapp.*, site.SiteName')
->join('site', 'site.SiteID = hostapp.SiteID', 'left');
if (!empty($filter['HostAppID'])) {
if (!ctype_digit((string) $filter['HostAppID'])) {
return $this->failValidationErrors('HostAppID filter must be a valid integer.');
}
$builder->where('hostapp.HostAppID', (int) $filter['HostAppID']);
}
if (!empty($filter['HostAppName'])) {
$builder->like('hostapp.HostAppName', $filter['HostAppName'], 'both');
}
$rows = $builder->findAll();
if (empty($rows)) {
return $this->respond(['status' => 'success', 'message' => 'no Data.', 'data' => []], 200);
}
return $this->respond(['status' => 'success', 'message' => 'fetch success', 'data' => $rows], 200);
}
public function show($HostAppID = null) {
$id = $this->requirePatchId($HostAppID, 'HostAppID');
if ($id === null) {
return;
}
$row = $this->model->select('hostapp.*, site.SiteName')
->join('site', 'site.SiteID = hostapp.SiteID', 'left')
->where('hostapp.HostAppID', $id)
->first();
if (empty($row)) {
return $this->respond(['status' => 'success', 'message' => 'no Data.', 'data' => null], 200);
}
return $this->respond(['status' => 'success', 'message' => 'fetch success', 'data' => $row], 200);
}
public function delete() {
try {
$input = $this->request->getJSON(true);
$id = $this->requirePatchId($input['HostAppID'] ?? null, 'HostAppID');
if ($id === null) {
return;
}
$this->model->delete($id);
return $this->respondDeleted(['status' => 'success', 'message' => "{$id} deleted successfully."]);
} catch (\Throwable $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
}
}
public function create() {
$input = $this->request->getJSON(true);
try {
unset($input['HostAppID']);
$id = $this->model->insert($input, true);
return $this->respondCreated(['status' => 'success', 'message' => 'data created successfully', 'data' => $id], 201);
} catch (\Throwable $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
}
}
public function update($HostAppID = null) {
$input = $this->requirePatchPayload($this->request->getJSON(true));
if ($input === null) {
return;
}
$id = $this->requirePatchId($HostAppID, 'HostAppID');
if ($id === null) {
return;
}
$existing = $this->model->find($id);
if (!$existing) {
return $this->respond(['status' => 'failed', 'message' => 'HostApp not found', 'data' => []], 404);
}
if (isset($input['HostAppID'])) {
if ((string) $input['HostAppID'] !== (string) $id) {
return $this->failValidationErrors('HostAppID in URL does not match body.');
}
unset($input['HostAppID']);
}
try {
$this->model->update($id, $input);
return $this->respond(['status' => 'success', 'message' => 'data updated successfully', 'data' => $id], 200);
} catch (\Throwable $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
}
}
}

View File

@ -1,135 +1,135 @@
<?php
namespace App\Controllers\Organization;
use App\Traits\PatchValidationTrait;
use App\Traits\ResponseTrait;
use App\Controllers\BaseController;
use App\Models\Organization\HostComParaModel;
class HostComParaController extends BaseController {
use ResponseTrait;
use PatchValidationTrait;
protected $db;
protected $model;
public function __construct() {
$this->db = \Config\Database::connect();
$this->model = new HostComParaModel();
}
public function index() {
$filter = [
'HostAppID' => $this->request->getVar('HostAppID'),
'HostIP' => $this->request->getVar('HostIP'),
];
$builder = $this->model->select('hostcompara.*, hostapp.HostAppName')
->join('hostapp', 'hostapp.HostAppID = hostcompara.HostAppID', 'left');
if (!empty($filter['HostAppID'])) {
if (!ctype_digit((string) $filter['HostAppID'])) {
return $this->failValidationErrors('HostAppID filter must be a valid integer.');
}
$builder->where('hostcompara.HostAppID', (int) $filter['HostAppID']);
}
if (!empty($filter['HostIP'])) {
$builder->like('hostcompara.HostIP', $filter['HostIP'], 'both');
}
$rows = $builder->findAll();
if (empty($rows)) {
return $this->respond(['status' => 'success', 'message' => 'no Data.', 'data' => []], 200);
}
return $this->respond(['status' => 'success', 'message' => 'fetch success', 'data' => $rows], 200);
}
public function show($HostAppID = null) {
$id = $this->requirePatchId($HostAppID, 'HostAppID');
if ($id === null) {
return;
}
$row = $this->model->select('hostcompara.*, hostapp.HostAppName')
->join('hostapp', 'hostapp.HostAppID = hostcompara.HostAppID', 'left')
->where('hostcompara.HostAppID', $id)
->first();
if (empty($row)) {
return $this->respond(['status' => 'success', 'message' => 'no Data.', 'data' => null], 200);
}
return $this->respond(['status' => 'success', 'message' => 'fetch success', 'data' => $row], 200);
}
public function delete() {
try {
$input = $this->request->getJSON(true);
$id = $this->requirePatchId($input['HostAppID'] ?? null, 'HostAppID');
if ($id === null) {
return;
}
$this->model->delete($id);
return $this->respondDeleted(['status' => 'success', 'message' => "{$id} deleted successfully."]);
} catch (\Throwable $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
}
}
public function create() {
$input = $this->request->getJSON(true);
try {
$hostAppId = $input['HostAppID'] ?? null;
if ($hostAppId === null) {
return $this->failValidationErrors('HostAppID is required.');
}
if (!ctype_digit((string) $hostAppId)) {
return $this->failValidationErrors('HostAppID must be a valid integer.');
}
$input['HostAppID'] = (int) $hostAppId;
$id = $this->model->insert($input, true);
return $this->respondCreated(['status' => 'success', 'message' => 'data created successfully', 'data' => $id], 201);
} catch (\Throwable $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
}
}
public function update($HostAppID = null) {
$input = $this->requirePatchPayload($this->request->getJSON(true));
if ($input === null) {
return;
}
$id = $this->requirePatchId($HostAppID, 'HostAppID');
if ($id === null) {
return;
}
$existing = $this->model->find($id);
if (!$existing) {
return $this->respond(['status' => 'failed', 'message' => 'HostComPara not found', 'data' => []], 404);
}
if (isset($input['HostAppID'])) {
if ((string) $input['HostAppID'] !== (string) $id) {
return $this->failValidationErrors('HostAppID in URL does not match body.');
}
unset($input['HostAppID']);
}
try {
$this->model->update($id, $input);
return $this->respond(['status' => 'success', 'message' => 'data updated successfully', 'data' => $id], 200);
} catch (\Throwable $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
}
}
}
<?php
namespace App\Controllers\Organization;
use App\Traits\PatchValidationTrait;
use App\Traits\ResponseTrait;
use App\Controllers\BaseController;
use App\Models\Organization\HostComParaModel;
class HostComParaController extends BaseController {
use ResponseTrait;
use PatchValidationTrait;
protected $db;
protected $model;
public function __construct() {
$this->db = \Config\Database::connect();
$this->model = new HostComParaModel();
}
public function index() {
$filter = [
'HostAppID' => $this->request->getVar('HostAppID'),
'HostIP' => $this->request->getVar('HostIP'),
];
$builder = $this->model->select('hostcompara.*, hostapp.HostAppName')
->join('hostapp', 'hostapp.HostAppID = hostcompara.HostAppID', 'left');
if (!empty($filter['HostAppID'])) {
if (!ctype_digit((string) $filter['HostAppID'])) {
return $this->failValidationErrors('HostAppID filter must be a valid integer.');
}
$builder->where('hostcompara.HostAppID', (int) $filter['HostAppID']);
}
if (!empty($filter['HostIP'])) {
$builder->like('hostcompara.HostIP', $filter['HostIP'], 'both');
}
$rows = $builder->findAll();
if (empty($rows)) {
return $this->respond(['status' => 'success', 'message' => 'no Data.', 'data' => []], 200);
}
return $this->respond(['status' => 'success', 'message' => 'fetch success', 'data' => $rows], 200);
}
public function show($HostAppID = null) {
$id = $this->requirePatchId($HostAppID, 'HostAppID');
if ($id === null) {
return;
}
$row = $this->model->select('hostcompara.*, hostapp.HostAppName')
->join('hostapp', 'hostapp.HostAppID = hostcompara.HostAppID', 'left')
->where('hostcompara.HostAppID', $id)
->first();
if (empty($row)) {
return $this->respond(['status' => 'success', 'message' => 'no Data.', 'data' => null], 200);
}
return $this->respond(['status' => 'success', 'message' => 'fetch success', 'data' => $row], 200);
}
public function delete() {
try {
$input = $this->request->getJSON(true);
$id = $this->requirePatchId($input['HostAppID'] ?? null, 'HostAppID');
if ($id === null) {
return;
}
$this->model->delete($id);
return $this->respondDeleted(['status' => 'success', 'message' => "{$id} deleted successfully."]);
} catch (\Throwable $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
}
}
public function create() {
$input = $this->request->getJSON(true);
try {
$hostAppId = $input['HostAppID'] ?? null;
if ($hostAppId === null) {
return $this->failValidationErrors('HostAppID is required.');
}
if (!ctype_digit((string) $hostAppId)) {
return $this->failValidationErrors('HostAppID must be a valid integer.');
}
$input['HostAppID'] = (int) $hostAppId;
$id = $this->model->insert($input, true);
return $this->respondCreated(['status' => 'success', 'message' => 'data created successfully', 'data' => $id], 201);
} catch (\Throwable $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
}
}
public function update($HostAppID = null) {
$input = $this->requirePatchPayload($this->request->getJSON(true));
if ($input === null) {
return;
}
$id = $this->requirePatchId($HostAppID, 'HostAppID');
if ($id === null) {
return;
}
$existing = $this->model->find($id);
if (!$existing) {
return $this->respond(['status' => 'failed', 'message' => 'HostComPara not found', 'data' => []], 404);
}
if (isset($input['HostAppID'])) {
if ((string) $input['HostAppID'] !== (string) $id) {
return $this->failValidationErrors('HostAppID in URL does not match body.');
}
unset($input['HostAppID']);
}
try {
$this->model->update($id, $input);
return $this->respond(['status' => 'success', 'message' => 'data updated successfully', 'data' => $id], 200);
} catch (\Throwable $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
}
}
}

View File

@ -1,306 +1,306 @@
<?php
namespace App\Controllers\User;
use App\Controllers\BaseController;
use App\Models\User\UserModel;
use App\Traits\PatchValidationTrait;
use App\Traits\ResponseTrait;
/**
* User Management Controller
* Handles CRUD operations for users
*/
class UserController extends BaseController
{
use ResponseTrait;
use PatchValidationTrait;
protected $model;
protected $db;
public function __construct()
{
$this->db = \Config\Database::connect();
$this->model = new UserModel();
}
/**
* List users with pagination and search
* GET /api/user?page=1&per_page=20&search=term
*/
public function index()
{
try {
$page = (int)($this->request->getGet('page') ?? 1);
$perPage = (int)($this->request->getGet('per_page') ?? 20);
$search = $this->request->getGet('search');
// Build query
$builder = $this->model->where('DelDate', null);
// Apply search if provided
if ($search) {
$builder->groupStart()
->like('Username', $search)
->orLike('Email', $search)
->orLike('Name', $search)
->groupEnd();
}
// Get total count for pagination
$total = $builder->countAllResults(false);
// Get paginated results
$users = $builder
->orderBy('UserID', 'DESC')
->limit($perPage, ($page - 1) * $perPage)
->findAll();
return $this->respond([
'status' => 'success',
'message' => 'Users retrieved successfully',
'data' => [
'users' => $users,
'pagination' => [
'current_page' => $page,
'per_page' => $perPage,
'total' => $total,
'total_pages' => ceil($total / $perPage)
]
]
], 200);
} catch (\Exception $e) {
log_message('error', 'UserController::index error: ' . $e->getMessage());
return $this->respond([
'status' => 'failed',
'message' => 'Failed to retrieve users',
'data' => null
], 500);
}
}
/**
* Get single user by ID
* GET /api/user/(:num)
*/
public function show($id)
{
try {
$user = $this->model->where('UserID', $id)
->where('DelDate', null)
->first();
if (empty($user)) {
return $this->respond([
'status' => 'failed',
'message' => 'User not found',
'data' => null
], 404);
}
return $this->respond([
'status' => 'success',
'message' => 'User retrieved successfully',
'data' => $user
], 200);
} catch (\Exception $e) {
log_message('error', 'UserController::show error: ' . $e->getMessage());
return $this->respond([
'status' => 'failed',
'message' => 'Failed to retrieve user',
'data' => null
], 500);
}
}
/**
* Create new user
* POST /api/user
*/
public function create()
{
try {
$data = $this->request->getJSON(true);
// Validation rules
$rules = [
'Username' => 'required|min_length[3]|max_length[50]|is_unique[users.Username]',
'Email' => 'required|valid_email|is_unique[users.Email]',
];
if (!$this->validateData($data, $rules)) {
return $this->respond([
'status' => 'failed',
'message' => 'Validation failed',
'data' => $this->validator->getErrors()
], 400);
}
// Set default values
$data['IsActive'] = $data['IsActive'] ?? true;
$data['CreatedAt'] = date('Y-m-d H:i:s');
$userId = $this->model->insert($data);
if (!$userId) {
return $this->respond([
'status' => 'failed',
'message' => 'Failed to create user',
'data' => null
], 500);
}
return $this->respond([
'status' => 'success',
'message' => 'User created successfully',
'data' => [
'UserID' => $userId,
'Username' => $data['Username'],
'Email' => $data['Email']
]
], 201);
} catch (\Exception $e) {
log_message('error', 'UserController::create error: ' . $e->getMessage());
return $this->respond([
'status' => 'failed',
'message' => 'Failed to create user',
'data' => null
], 500);
}
}
/**
* Update existing user
* PATCH /api/user/(:num)
*/
public function update($id)
{
try {
$data = $this->requirePatchPayload($this->request->getJSON(true));
if ($data === null) {
return;
}
if (empty($id) || !ctype_digit((string) $id)) {
return $this->respond([
'status' => 'failed',
'message' => 'UserID is required',
'data' => null
], 400);
}
if (isset($data['UserID']) && (string) $data['UserID'] !== (string) $id) {
return $this->respond([
'status' => 'failed',
'message' => 'UserID in URL does not match body',
'data' => null
], 400);
}
$userId = (int) $id;
// Check if user exists
$user = $this->model->where('UserID', $userId)
->where('DelDate', null)
->first();
if (empty($user)) {
return $this->respond([
'status' => 'failed',
'message' => 'User not found',
'data' => null
], 404);
}
// Remove UserID from data array
unset($data['UserID']);
// Don't allow updating these fields
unset($data['CreatedAt']);
unset($data['Username']); // Username should not change
$data['UpdatedAt'] = date('Y-m-d H:i:s');
$updated = $this->model->update($userId, $data);
if (!$updated) {
return $this->respond([
'status' => 'failed',
'message' => 'Failed to update user',
'data' => null
], 500);
}
return $this->respond([
'status' => 'success',
'message' => 'User updated successfully',
'data' => [
'UserID' => $userId,
'updated_fields' => array_keys($data)
]
], 200);
} catch (\Exception $e) {
log_message('error', 'UserController::update error: ' . $e->getMessage());
return $this->respond([
'status' => 'failed',
'message' => 'Failed to update user',
'data' => null
], 500);
}
}
/**
* Delete user (soft delete)
* DELETE /api/user/(:num)
*/
public function delete($id)
{
try {
// Check if user exists
$user = $this->model->where('UserID', $id)
->where('DelDate', null)
->first();
if (empty($user)) {
return $this->respond([
'status' => 'failed',
'message' => 'User not found',
'data' => null
], 404);
}
// Soft delete by setting DelDate
$deleted = $this->model->update($id, [
'DelDate' => date('Y-m-d H:i:s'),
'IsActive' => false
]);
if (!$deleted) {
return $this->respond([
'status' => 'failed',
'message' => 'Failed to delete user',
'data' => null
], 500);
}
return $this->respond([
'status' => 'success',
'message' => 'User deleted successfully',
'data' => ['UserID' => $id]
], 200);
} catch (\Exception $e) {
log_message('error', 'UserController::delete error: ' . $e->getMessage());
return $this->respond([
'status' => 'failed',
'message' => 'Failed to delete user',
'data' => null
], 500);
}
}
}
<?php
namespace App\Controllers\User;
use App\Controllers\BaseController;
use App\Models\User\UserModel;
use App\Traits\PatchValidationTrait;
use App\Traits\ResponseTrait;
/**
* User Management Controller
* Handles CRUD operations for users
*/
class UserController extends BaseController
{
use ResponseTrait;
use PatchValidationTrait;
protected $model;
protected $db;
public function __construct()
{
$this->db = \Config\Database::connect();
$this->model = new UserModel();
}
/**
* List users with pagination and search
* GET /api/user?page=1&per_page=20&search=term
*/
public function index()
{
try {
$page = (int)($this->request->getGet('page') ?? 1);
$perPage = (int)($this->request->getGet('per_page') ?? 20);
$search = $this->request->getGet('search');
// Build query
$builder = $this->model->where('DelDate', null);
// Apply search if provided
if ($search) {
$builder->groupStart()
->like('Username', $search)
->orLike('Email', $search)
->orLike('Name', $search)
->groupEnd();
}
// Get total count for pagination
$total = $builder->countAllResults(false);
// Get paginated results
$users = $builder
->orderBy('UserID', 'DESC')
->limit($perPage, ($page - 1) * $perPage)
->findAll();
return $this->respond([
'status' => 'success',
'message' => 'Users retrieved successfully',
'data' => [
'users' => $users,
'pagination' => [
'current_page' => $page,
'per_page' => $perPage,
'total' => $total,
'total_pages' => ceil($total / $perPage)
]
]
], 200);
} catch (\Exception $e) {
log_message('error', 'UserController::index error: ' . $e->getMessage());
return $this->respond([
'status' => 'failed',
'message' => 'Failed to retrieve users',
'data' => null
], 500);
}
}
/**
* Get single user by ID
* GET /api/user/(:num)
*/
public function show($id)
{
try {
$user = $this->model->where('UserID', $id)
->where('DelDate', null)
->first();
if (empty($user)) {
return $this->respond([
'status' => 'failed',
'message' => 'User not found',
'data' => null
], 404);
}
return $this->respond([
'status' => 'success',
'message' => 'User retrieved successfully',
'data' => $user
], 200);
} catch (\Exception $e) {
log_message('error', 'UserController::show error: ' . $e->getMessage());
return $this->respond([
'status' => 'failed',
'message' => 'Failed to retrieve user',
'data' => null
], 500);
}
}
/**
* Create new user
* POST /api/user
*/
public function create()
{
try {
$data = $this->request->getJSON(true);
// Validation rules
$rules = [
'Username' => 'required|min_length[3]|max_length[50]|is_unique[users.Username]',
'Email' => 'required|valid_email|is_unique[users.Email]',
];
if (!$this->validateData($data, $rules)) {
return $this->respond([
'status' => 'failed',
'message' => 'Validation failed',
'data' => $this->validator->getErrors()
], 400);
}
// Set default values
$data['IsActive'] = $data['IsActive'] ?? true;
$data['CreatedAt'] = date('Y-m-d H:i:s');
$userId = $this->model->insert($data);
if (!$userId) {
return $this->respond([
'status' => 'failed',
'message' => 'Failed to create user',
'data' => null
], 500);
}
return $this->respond([
'status' => 'success',
'message' => 'User created successfully',
'data' => [
'UserID' => $userId,
'Username' => $data['Username'],
'Email' => $data['Email']
]
], 201);
} catch (\Exception $e) {
log_message('error', 'UserController::create error: ' . $e->getMessage());
return $this->respond([
'status' => 'failed',
'message' => 'Failed to create user',
'data' => null
], 500);
}
}
/**
* Update existing user
* PATCH /api/user/(:num)
*/
public function update($id)
{
try {
$data = $this->requirePatchPayload($this->request->getJSON(true));
if ($data === null) {
return;
}
if (empty($id) || !ctype_digit((string) $id)) {
return $this->respond([
'status' => 'failed',
'message' => 'UserID is required',
'data' => null
], 400);
}
if (isset($data['UserID']) && (string) $data['UserID'] !== (string) $id) {
return $this->respond([
'status' => 'failed',
'message' => 'UserID in URL does not match body',
'data' => null
], 400);
}
$userId = (int) $id;
// Check if user exists
$user = $this->model->where('UserID', $userId)
->where('DelDate', null)
->first();
if (empty($user)) {
return $this->respond([
'status' => 'failed',
'message' => 'User not found',
'data' => null
], 404);
}
// Remove UserID from data array
unset($data['UserID']);
// Don't allow updating these fields
unset($data['CreatedAt']);
unset($data['Username']); // Username should not change
$data['UpdatedAt'] = date('Y-m-d H:i:s');
$updated = $this->model->update($userId, $data);
if (!$updated) {
return $this->respond([
'status' => 'failed',
'message' => 'Failed to update user',
'data' => null
], 500);
}
return $this->respond([
'status' => 'success',
'message' => 'User updated successfully',
'data' => [
'UserID' => $userId,
'updated_fields' => array_keys($data)
]
], 200);
} catch (\Exception $e) {
log_message('error', 'UserController::update error: ' . $e->getMessage());
return $this->respond([
'status' => 'failed',
'message' => 'Failed to update user',
'data' => null
], 500);
}
}
/**
* Delete user (soft delete)
* DELETE /api/user/(:num)
*/
public function delete($id)
{
try {
// Check if user exists
$user = $this->model->where('UserID', $id)
->where('DelDate', null)
->first();
if (empty($user)) {
return $this->respond([
'status' => 'failed',
'message' => 'User not found',
'data' => null
], 404);
}
// Soft delete by setting DelDate
$deleted = $this->model->update($id, [
'DelDate' => date('Y-m-d H:i:s'),
'IsActive' => false
]);
if (!$deleted) {
return $this->respond([
'status' => 'failed',
'message' => 'Failed to delete user',
'data' => null
], 500);
}
return $this->respond([
'status' => 'success',
'message' => 'User deleted successfully',
'data' => ['UserID' => $id]
], 200);
} catch (\Exception $e) {
log_message('error', 'UserController::delete error: ' . $e->getMessage());
return $this->respond([
'status' => 'failed',
'message' => 'Failed to delete user',
'data' => null
], 500);
}
}
}

View File

@ -1,50 +1,50 @@
<?php
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class CreateHostAppAndCodingSys extends Migration {
public function up() {
// Table: hostapp
$this->forge->addField([
'HostAppID' => ['type' => 'INT', 'unsigned' => true, 'auto_increment' => true],
'HostAppName' => ['type' => 'VARCHAR', 'constraint' => 255, 'null' => false],
'SiteID' => ['type' => 'INT', 'unsigned' => true, 'null' => true],
'CreateDate' => ['type' => 'DATETIME', 'null' => true],
'EndDate' => ['type' => 'DATETIME', 'null' => true]
]);
$this->forge->addKey('HostAppID', true);
$this->forge->createTable('hostapp');
// Table: hostcompara
$this->forge->addField([
'HostAppID' => ['type' => 'INT', 'unsigned' => true, 'auto_increment' => true],
'HostIP' => ['type' => 'VARCHAR', 'constraint' => 15, 'null' => true],
'HostPort' => ['type' => 'VARCHAR', 'constraint' => 6, 'null' => true],
'HostPwd' => ['type' => 'VARCHAR', 'constraint' => 255, 'null' => true],
'CreateDate' => ['type' => 'DATETIME', 'null' => true],
'EndDate' => ['type' => 'DATETIME', 'null' => true]
]);
$this->forge->addKey('HostAppID', true);
$this->forge->createTable('hostcompara');
// Table: codingsys
$this->forge->addField([
'CodingSysID' => ['type' => 'INT', 'unsigned' => true, 'auto_increment' => true],
'CodingSysAbb' => ['type' => 'VARCHAR', 'constraint' => 6, 'null' => false, 'unique' => true],
'FullText' => ['type' => 'VARCHAR', 'constraint' => 255, 'null' => true],
'Description' => ['type' => 'VARCHAR', 'constraint' => 255, 'null' => true],
'CreateDate' => ['type' => 'DATETIME', 'null' => true],
'EndDate' => ['type' => 'DATETIME', 'null' => true]
]);
$this->forge->addKey('CodingSysID', true);
$this->forge->createTable('codingsys');
}
public function down() {
$this->forge->dropTable('codingsys');
$this->forge->dropTable('hostcompara');
$this->forge->dropTable('hostapp');
}
}
<?php
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class CreateHostAppAndCodingSys extends Migration {
public function up() {
// Table: hostapp
$this->forge->addField([
'HostAppID' => ['type' => 'INT', 'unsigned' => true, 'auto_increment' => true],
'HostAppName' => ['type' => 'VARCHAR', 'constraint' => 255, 'null' => false],
'SiteID' => ['type' => 'INT', 'unsigned' => true, 'null' => true],
'CreateDate' => ['type' => 'DATETIME', 'null' => true],
'EndDate' => ['type' => 'DATETIME', 'null' => true]
]);
$this->forge->addKey('HostAppID', true);
$this->forge->createTable('hostapp');
// Table: hostcompara
$this->forge->addField([
'HostAppID' => ['type' => 'INT', 'unsigned' => true, 'auto_increment' => true],
'HostIP' => ['type' => 'VARCHAR', 'constraint' => 15, 'null' => true],
'HostPort' => ['type' => 'VARCHAR', 'constraint' => 6, 'null' => true],
'HostPwd' => ['type' => 'VARCHAR', 'constraint' => 255, 'null' => true],
'CreateDate' => ['type' => 'DATETIME', 'null' => true],
'EndDate' => ['type' => 'DATETIME', 'null' => true]
]);
$this->forge->addKey('HostAppID', true);
$this->forge->createTable('hostcompara');
// Table: codingsys
$this->forge->addField([
'CodingSysID' => ['type' => 'INT', 'unsigned' => true, 'auto_increment' => true],
'CodingSysAbb' => ['type' => 'VARCHAR', 'constraint' => 6, 'null' => false, 'unique' => true],
'FullText' => ['type' => 'VARCHAR', 'constraint' => 255, 'null' => true],
'Description' => ['type' => 'VARCHAR', 'constraint' => 255, 'null' => true],
'CreateDate' => ['type' => 'DATETIME', 'null' => true],
'EndDate' => ['type' => 'DATETIME', 'null' => true]
]);
$this->forge->addKey('CodingSysID', true);
$this->forge->createTable('codingsys');
}
public function down() {
$this->forge->dropTable('codingsys');
$this->forge->dropTable('hostcompara');
$this->forge->dropTable('hostapp');
}
}

View File

@ -1,137 +1,137 @@
<?php
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class CreateAuditLogs extends Migration
{
private array $logTables = [
'logpatient' => 'LogPatientID',
'logorder' => 'LogOrderID',
'logmaster' => 'LogMasterID',
'logsystem' => 'LogSystemID',
];
public function up(): void
{
foreach ($this->logTables as $table => $pk) {
$this->createLogTable($table, $pk);
}
}
public function down(): void
{
foreach (array_reverse($this->logTables) as $table => $pk) {
$this->forge->dropTable($table, true);
}
}
private function createLogTable(string $table, string $primaryKey): void
{
$fields = [
$primaryKey => [
'type' => 'BIGINT',
'constraint' => 20,
'unsigned' => true,
'auto_increment' => true,
],
'TblName' => [
'type' => 'VARCHAR',
'constraint' => 64,
],
'RecID' => [
'type' => 'VARCHAR',
'constraint' => 64,
],
'FldName' => [
'type' => 'VARCHAR',
'constraint' => 128,
'null' => true,
],
'FldValuePrev' => [
'type' => 'TEXT',
'null' => true,
],
'FldValueNew' => [
'type' => 'TEXT',
'null' => true,
],
'UserID' => [
'type' => 'VARCHAR',
'constraint' => 64,
],
'SiteID' => [
'type' => 'VARCHAR',
'constraint' => 32,
],
'DIDType' => [
'type' => 'VARCHAR',
'constraint' => 32,
'null' => true,
],
'DID' => [
'type' => 'VARCHAR',
'constraint' => 128,
'null' => true,
],
'MachineID' => [
'type' => 'VARCHAR',
'constraint' => 128,
'null' => true,
],
'SessionID' => [
'type' => 'VARCHAR',
'constraint' => 128,
],
'AppID' => [
'type' => 'VARCHAR',
'constraint' => 64,
],
'ProcessID' => [
'type' => 'VARCHAR',
'constraint' => 128,
'null' => true,
],
'WebPageID' => [
'type' => 'VARCHAR',
'constraint' => 128,
'null' => true,
],
'EventID' => [
'type' => 'VARCHAR',
'constraint' => 80,
],
'ActivityID' => [
'type' => 'VARCHAR',
'constraint' => 24,
],
'Reason' => [
'type' => 'VARCHAR',
'constraint' => 512,
'null' => true,
],
'LogDate' => [
'type' => 'DATETIME',
'constraint' => 3,
],
'Context' => [
'type' => 'JSON',
],
'IpAddress' => [
'type' => 'VARCHAR',
'constraint' => 45,
'null' => true,
],
];
$this->forge->addField($fields);
$this->forge->addKey($primaryKey, true);
$this->forge->addKey(['LogDate'], false, false, "idx_{$table}_logdate");
$this->forge->addKey(['RecID', 'LogDate'], false, false, "idx_{$table}_recid_logdate");
$this->forge->addKey(['UserID', 'LogDate'], false, false, "idx_{$table}_userid_logdate");
$this->forge->addKey(['EventID', 'LogDate'], false, false, "idx_{$table}_eventid_logdate");
$this->forge->addKey(['SiteID', 'LogDate'], false, false, "idx_{$table}_site_logdate");
$this->forge->createTable($table, true);
}
}
<?php
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class CreateAuditLogs extends Migration
{
private array $logTables = [
'logpatient' => 'LogPatientID',
'logorder' => 'LogOrderID',
'logmaster' => 'LogMasterID',
'logsystem' => 'LogSystemID',
];
public function up(): void
{
foreach ($this->logTables as $table => $pk) {
$this->createLogTable($table, $pk);
}
}
public function down(): void
{
foreach (array_reverse($this->logTables) as $table => $pk) {
$this->forge->dropTable($table, true);
}
}
private function createLogTable(string $table, string $primaryKey): void
{
$fields = [
$primaryKey => [
'type' => 'BIGINT',
'constraint' => 20,
'unsigned' => true,
'auto_increment' => true,
],
'TblName' => [
'type' => 'VARCHAR',
'constraint' => 64,
],
'RecID' => [
'type' => 'VARCHAR',
'constraint' => 64,
],
'FldName' => [
'type' => 'VARCHAR',
'constraint' => 128,
'null' => true,
],
'FldValuePrev' => [
'type' => 'TEXT',
'null' => true,
],
'FldValueNew' => [
'type' => 'TEXT',
'null' => true,
],
'UserID' => [
'type' => 'VARCHAR',
'constraint' => 64,
],
'SiteID' => [
'type' => 'VARCHAR',
'constraint' => 32,
],
'DIDType' => [
'type' => 'VARCHAR',
'constraint' => 32,
'null' => true,
],
'DID' => [
'type' => 'VARCHAR',
'constraint' => 128,
'null' => true,
],
'MachineID' => [
'type' => 'VARCHAR',
'constraint' => 128,
'null' => true,
],
'SessionID' => [
'type' => 'VARCHAR',
'constraint' => 128,
],
'AppID' => [
'type' => 'VARCHAR',
'constraint' => 64,
],
'ProcessID' => [
'type' => 'VARCHAR',
'constraint' => 128,
'null' => true,
],
'WebPageID' => [
'type' => 'VARCHAR',
'constraint' => 128,
'null' => true,
],
'EventID' => [
'type' => 'VARCHAR',
'constraint' => 80,
],
'ActivityID' => [
'type' => 'VARCHAR',
'constraint' => 24,
],
'Reason' => [
'type' => 'VARCHAR',
'constraint' => 512,
'null' => true,
],
'LogDate' => [
'type' => 'DATETIME',
'constraint' => 3,
],
'Context' => [
'type' => 'JSON',
],
'IpAddress' => [
'type' => 'VARCHAR',
'constraint' => 45,
'null' => true,
],
];
$this->forge->addField($fields);
$this->forge->addKey($primaryKey, true);
$this->forge->addKey(['LogDate'], false, false, "idx_{$table}_logdate");
$this->forge->addKey(['RecID', 'LogDate'], false, false, "idx_{$table}_recid_logdate");
$this->forge->addKey(['UserID', 'LogDate'], false, false, "idx_{$table}_userid_logdate");
$this->forge->addKey(['EventID', 'LogDate'], false, false, "idx_{$table}_eventid_logdate");
$this->forge->addKey(['SiteID', 'LogDate'], false, false, "idx_{$table}_site_logdate");
$this->forge->createTable($table, true);
}
}

View File

@ -1,222 +1,222 @@
<?php
namespace App\Database\Seeds;
use CodeIgniter\Database\Seeder;
class HostAppCodingSysSeeder extends Seeder
{
public function run()
{
$now = date('Y-m-d H:i:s');
// Clear existing data first (avoid foreign key constraint issues by ordering)
$this->db->table('hostcompara')->emptyTable();
$this->db->table('hostapp')->emptyTable();
$this->db->table('codingsys')->emptyTable();
// HostApp - Host Applications
$hostAppData = [
[
'HostAppID' => 1,
'HostAppName' => 'Laboratory Information System Main',
'SiteID' => 1,
'CreateDate' => $now,
'EndDate' => null
],
[
'HostAppID' => 2,
'HostAppName' => 'Laboratory Information System Backup',
'SiteID' => 1,
'CreateDate' => $now,
'EndDate' => null
],
[
'HostAppID' => 3,
'HostAppName' => 'Electronic Medical Record System',
'SiteID' => 1,
'CreateDate' => $now,
'EndDate' => null
],
[
'HostAppID' => 4,
'HostAppName' => 'Picture Archiving System',
'SiteID' => 1,
'CreateDate' => $now,
'EndDate' => null
],
[
'HostAppID' => 5,
'HostAppName' => 'Billing System',
'SiteID' => 1,
'CreateDate' => $now,
'EndDate' => null
],
[
'HostAppID' => 6,
'HostAppName' => 'Insurance System Integration',
'SiteID' => 1,
'CreateDate' => $now,
'EndDate' => null
],
[
'HostAppID' => 7,
'HostAppName' => 'Legacy Laboratory System',
'SiteID' => 1,
'CreateDate' => $now,
'EndDate' => date('Y-m-d H:i:s', strtotime('-1 year'))
],
];
$this->db->table('hostapp')->insertBatch($hostAppData);
// HostComPara - Host Communication Parameters
$hostComParaData = [
[
'HostAppID' => 1,
'HostIP' => '192.168.1.10',
'HostPort' => '8080',
'HostPwd' => 'lis_main_pass_2024',
'CreateDate' => $now,
'EndDate' => null
],
[
'HostAppID' => 2,
'HostIP' => '192.168.1.11',
'HostPort' => '8081',
'HostPwd' => 'lis_backup_pass_2024',
'CreateDate' => $now,
'EndDate' => null
],
[
'HostAppID' => 3,
'HostIP' => '192.168.1.20',
'HostPort' => '443',
'HostPwd' => 'emr_secure_2024',
'CreateDate' => $now,
'EndDate' => null
],
[
'HostAppID' => 4,
'HostIP' => '192.168.1.30',
'HostPort' => '8042',
'HostPwd' => 'pacs_dicom_2024',
'CreateDate' => $now,
'EndDate' => null
],
[
'HostAppID' => 5,
'HostIP' => '192.168.1.40',
'HostPort' => '8443',
'HostPwd' => 'bill_payment_2024',
'CreateDate' => $now,
'EndDate' => null
],
[
'HostAppID' => 6,
'HostIP' => '192.168.1.50',
'HostPort' => '443',
'HostPwd' => 'ins_claim_2024',
'CreateDate' => $now,
'EndDate' => null
],
[
'HostAppID' => 7,
'HostIP' => '192.168.1.99',
'HostPort' => '8080',
'HostPwd' => 'old_legacy_pass',
'CreateDate' => $now,
'EndDate' => date('Y-m-d H:i:s', strtotime('-1 year'))
],
];
$this->db->table('hostcompara')->insertBatch($hostComParaData);
// CodingSys - Coding Systems
$codingSysData = [
[
'CodingSysAbb' => 'ICD10',
'FullText' => 'International Classification of Diseases 10th Revision',
'Description' => 'Medical diagnosis coding system for diseases and health conditions',
'CreateDate' => $now,
'EndDate' => null
],
[
'CodingSysAbb' => 'ICD10PCS',
'FullText' => 'ICD-10 Procedure Coding System',
'Description' => 'Classification system for inpatient hospital procedures',
'CreateDate' => $now,
'EndDate' => null
],
[
'CodingSysAbb' => 'LOINC',
'FullText' => 'Logical Observation Identifiers Names and Codes',
'Description' => 'Standard for identifying medical laboratory observations',
'CreateDate' => $now,
'EndDate' => null
],
[
'CodingSysAbb' => 'SNOMED',
'FullText' => 'SNOMED CT',
'Description' => 'Systematized Nomenclature of Medicine - Clinical Terms',
'CreateDate' => $now,
'EndDate' => null
],
[
'CodingSysAbb' => 'CPT',
'FullText' => 'Current Procedural Terminology',
'Description' => 'Medical code set for medical procedures and services',
'CreateDate' => $now,
'EndDate' => null
],
[
'CodingSysAbb' => 'HCPCS',
'FullText' => 'Healthcare Common Procedure Coding System',
'Description' => 'Medical code set for equipment, supplies, and services',
'CreateDate' => $now,
'EndDate' => null
],
[
'CodingSysAbb' => 'RXNORM',
'FullText' => 'RxNorm',
'Description' => 'Normalized naming system for medications',
'CreateDate' => $now,
'EndDate' => null
],
[
'CodingSysAbb' => 'NDC',
'FullText' => 'National Drug Code',
'Description' => 'Unique identifier for human drugs in the United States',
'CreateDate' => $now,
'EndDate' => null
],
[
'CodingSysAbb' => 'UCUM',
'FullText' => 'Unified Code for Units of Measure',
'Description' => 'Standard for units of measurement in clinical and scientific contexts',
'CreateDate' => $now,
'EndDate' => null
],
[
'CodingSysAbb' => 'CVX',
'FullText' => 'Vaccines Administered',
'Description' => 'Vaccine codes for immunization records',
'CreateDate' => $now,
'EndDate' => null
],
[
'CodingSysAbb' => 'ICD9',
'FullText' => 'International Classification of Diseases 9th Revision',
'Description' => 'Legacy medical diagnosis coding system',
'CreateDate' => $now,
'EndDate' => date('Y-m-d H:i:s', strtotime('-2 years'))
],
[
'CodingSysAbb' => 'ICD9CM',
'FullText' => 'ICD-9-CM',
'Description' => 'Legacy procedure coding system',
'CreateDate' => $now,
'EndDate' => date('Y-m-d H:i:s', strtotime('-2 years'))
],
];
$this->db->table('codingsys')->insertBatch($codingSysData);
}
}
<?php
namespace App\Database\Seeds;
use CodeIgniter\Database\Seeder;
class HostAppCodingSysSeeder extends Seeder
{
public function run()
{
$now = date('Y-m-d H:i:s');
// Clear existing data first (avoid foreign key constraint issues by ordering)
$this->db->table('hostcompara')->emptyTable();
$this->db->table('hostapp')->emptyTable();
$this->db->table('codingsys')->emptyTable();
// HostApp - Host Applications
$hostAppData = [
[
'HostAppID' => 1,
'HostAppName' => 'Laboratory Information System Main',
'SiteID' => 1,
'CreateDate' => $now,
'EndDate' => null
],
[
'HostAppID' => 2,
'HostAppName' => 'Laboratory Information System Backup',
'SiteID' => 1,
'CreateDate' => $now,
'EndDate' => null
],
[
'HostAppID' => 3,
'HostAppName' => 'Electronic Medical Record System',
'SiteID' => 1,
'CreateDate' => $now,
'EndDate' => null
],
[
'HostAppID' => 4,
'HostAppName' => 'Picture Archiving System',
'SiteID' => 1,
'CreateDate' => $now,
'EndDate' => null
],
[
'HostAppID' => 5,
'HostAppName' => 'Billing System',
'SiteID' => 1,
'CreateDate' => $now,
'EndDate' => null
],
[
'HostAppID' => 6,
'HostAppName' => 'Insurance System Integration',
'SiteID' => 1,
'CreateDate' => $now,
'EndDate' => null
],
[
'HostAppID' => 7,
'HostAppName' => 'Legacy Laboratory System',
'SiteID' => 1,
'CreateDate' => $now,
'EndDate' => date('Y-m-d H:i:s', strtotime('-1 year'))
],
];
$this->db->table('hostapp')->insertBatch($hostAppData);
// HostComPara - Host Communication Parameters
$hostComParaData = [
[
'HostAppID' => 1,
'HostIP' => '192.168.1.10',
'HostPort' => '8080',
'HostPwd' => 'lis_main_pass_2024',
'CreateDate' => $now,
'EndDate' => null
],
[
'HostAppID' => 2,
'HostIP' => '192.168.1.11',
'HostPort' => '8081',
'HostPwd' => 'lis_backup_pass_2024',
'CreateDate' => $now,
'EndDate' => null
],
[
'HostAppID' => 3,
'HostIP' => '192.168.1.20',
'HostPort' => '443',
'HostPwd' => 'emr_secure_2024',
'CreateDate' => $now,
'EndDate' => null
],
[
'HostAppID' => 4,
'HostIP' => '192.168.1.30',
'HostPort' => '8042',
'HostPwd' => 'pacs_dicom_2024',
'CreateDate' => $now,
'EndDate' => null
],
[
'HostAppID' => 5,
'HostIP' => '192.168.1.40',
'HostPort' => '8443',
'HostPwd' => 'bill_payment_2024',
'CreateDate' => $now,
'EndDate' => null
],
[
'HostAppID' => 6,
'HostIP' => '192.168.1.50',
'HostPort' => '443',
'HostPwd' => 'ins_claim_2024',
'CreateDate' => $now,
'EndDate' => null
],
[
'HostAppID' => 7,
'HostIP' => '192.168.1.99',
'HostPort' => '8080',
'HostPwd' => 'old_legacy_pass',
'CreateDate' => $now,
'EndDate' => date('Y-m-d H:i:s', strtotime('-1 year'))
],
];
$this->db->table('hostcompara')->insertBatch($hostComParaData);
// CodingSys - Coding Systems
$codingSysData = [
[
'CodingSysAbb' => 'ICD10',
'FullText' => 'International Classification of Diseases 10th Revision',
'Description' => 'Medical diagnosis coding system for diseases and health conditions',
'CreateDate' => $now,
'EndDate' => null
],
[
'CodingSysAbb' => 'ICD10PCS',
'FullText' => 'ICD-10 Procedure Coding System',
'Description' => 'Classification system for inpatient hospital procedures',
'CreateDate' => $now,
'EndDate' => null
],
[
'CodingSysAbb' => 'LOINC',
'FullText' => 'Logical Observation Identifiers Names and Codes',
'Description' => 'Standard for identifying medical laboratory observations',
'CreateDate' => $now,
'EndDate' => null
],
[
'CodingSysAbb' => 'SNOMED',
'FullText' => 'SNOMED CT',
'Description' => 'Systematized Nomenclature of Medicine - Clinical Terms',
'CreateDate' => $now,
'EndDate' => null
],
[
'CodingSysAbb' => 'CPT',
'FullText' => 'Current Procedural Terminology',
'Description' => 'Medical code set for medical procedures and services',
'CreateDate' => $now,
'EndDate' => null
],
[
'CodingSysAbb' => 'HCPCS',
'FullText' => 'Healthcare Common Procedure Coding System',
'Description' => 'Medical code set for equipment, supplies, and services',
'CreateDate' => $now,
'EndDate' => null
],
[
'CodingSysAbb' => 'RXNORM',
'FullText' => 'RxNorm',
'Description' => 'Normalized naming system for medications',
'CreateDate' => $now,
'EndDate' => null
],
[
'CodingSysAbb' => 'NDC',
'FullText' => 'National Drug Code',
'Description' => 'Unique identifier for human drugs in the United States',
'CreateDate' => $now,
'EndDate' => null
],
[
'CodingSysAbb' => 'UCUM',
'FullText' => 'Unified Code for Units of Measure',
'Description' => 'Standard for units of measurement in clinical and scientific contexts',
'CreateDate' => $now,
'EndDate' => null
],
[
'CodingSysAbb' => 'CVX',
'FullText' => 'Vaccines Administered',
'Description' => 'Vaccine codes for immunization records',
'CreateDate' => $now,
'EndDate' => null
],
[
'CodingSysAbb' => 'ICD9',
'FullText' => 'International Classification of Diseases 9th Revision',
'Description' => 'Legacy medical diagnosis coding system',
'CreateDate' => $now,
'EndDate' => date('Y-m-d H:i:s', strtotime('-2 years'))
],
[
'CodingSysAbb' => 'ICD9CM',
'FullText' => 'ICD-9-CM',
'Description' => 'Legacy procedure coding system',
'CreateDate' => $now,
'EndDate' => date('Y-m-d H:i:s', strtotime('-2 years'))
],
];
$this->db->table('codingsys')->insertBatch($codingSysData);
}
}

View File

@ -1,9 +1,9 @@
{"name": "activity_result",
"VSName": "Activity Result",
"VCategory": "System",
"values": [
{"key": "0", "value": "Failed"},
{"key": "1", "value": "Success with note"},
{"key": "2", "value": "Success"}
]
}
{"name": "activity_result",
"VSName": "Activity Result",
"VCategory": "System",
"values": [
{"key": "0", "value": "Failed"},
{"key": "1", "value": "Success with note"},
{"key": "2", "value": "Success"}
]
}

View File

@ -1,25 +1,25 @@
{"name": "additive",
"VSName": "Additive",
"VCategory": "System",
"values": [
{"key": "Hep", "value": "Heparin ammonium"},
{"key": "Apro", "value": "Aprotinin"},
{"key": "HepCa", "value": "Heparin calcium"},
{"key": "H3BO3", "value": "Boric acid"},
{"key": "CaOxa", "value": "Calcium oxalate"},
{"key": "EDTA", "value": "EDTA"},
{"key": "Ede", "value": "Edetate"},
{"key": "HCl", "value": "Hydrochloric acid"},
{"key": "Hrdn", "value": "Hirudin"},
{"key": "EdeK", "value": "Edetate dipotassium"},
{"key": "EdeTri", "value": "Tripotassium edetate"},
{"key": "LiHep", "value": "Heparin lithium"},
{"key": "EdeNa", "value": "Edetate disodium"},
{"key": "NaCtrt", "value": "Sodium citrate"},
{"key": "NaHep", "value": "Heparin sodium"},
{"key": "NaF", "value": "Sodium fluoride"},
{"key": "Borax", "value": "Sodium tetraborate"},
{"key": "Mntl", "value": "Mannitol"},
{"key": "NaFrm", "value": "Sodium formate"}
]
}
{"name": "additive",
"VSName": "Additive",
"VCategory": "System",
"values": [
{"key": "Hep", "value": "Heparin ammonium"},
{"key": "Apro", "value": "Aprotinin"},
{"key": "HepCa", "value": "Heparin calcium"},
{"key": "H3BO3", "value": "Boric acid"},
{"key": "CaOxa", "value": "Calcium oxalate"},
{"key": "EDTA", "value": "EDTA"},
{"key": "Ede", "value": "Edetate"},
{"key": "HCl", "value": "Hydrochloric acid"},
{"key": "Hrdn", "value": "Hirudin"},
{"key": "EdeK", "value": "Edetate dipotassium"},
{"key": "EdeTri", "value": "Tripotassium edetate"},
{"key": "LiHep", "value": "Heparin lithium"},
{"key": "EdeNa", "value": "Edetate disodium"},
{"key": "NaCtrt", "value": "Sodium citrate"},
{"key": "NaHep", "value": "Heparin sodium"},
{"key": "NaF", "value": "Sodium fluoride"},
{"key": "Borax", "value": "Sodium tetraborate"},
{"key": "Mntl", "value": "Mannitol"},
{"key": "NaFrm", "value": "Sodium formate"}
]
}

View File

@ -1,9 +1,9 @@
{"name": "area_class",
"VSName": "Area Class",
"VCategory": "System",
"values": [
{"key": "PROP", "value": "Propinsi"},
{"key": "KAB", "value": "Kabupaten"},
{"key": "KOTA", "value": "Kota"}
]
}
{"name": "area_class",
"VSName": "Area Class",
"VCategory": "System",
"values": [
{"key": "PROP", "value": "Propinsi"},
{"key": "KAB", "value": "Kabupaten"},
{"key": "KOTA", "value": "Kota"}
]
}

View File

@ -1,10 +1,10 @@
{"name": "body_site",
"VSName": "Body Site",
"VCategory": "System",
"values": [
{"key": "LA", "value": "Left Arm"},
{"key": "RA", "value": "Right Arm"},
{"key": "LF", "value": "Left Foot"},
{"key": "RF", "value": "Right Foot"}
]
}
{"name": "body_site",
"VSName": "Body Site",
"VCategory": "System",
"values": [
{"key": "LA", "value": "Left Arm"},
{"key": "RA", "value": "Right Arm"},
{"key": "LF", "value": "Left Foot"},
{"key": "RF", "value": "Right Foot"}
]
}

View File

@ -1,16 +1,16 @@
{"name": "collection_method",
"VSName": "Collection Method",
"VCategory": "System",
"values": [
{"key": "pcntr", "value": "Puncture"},
{"key": "fprk", "value": "Finger-prick sampling"},
{"key": "ucct", "value": "Urine specimen collection, clean catch"},
{"key": "utcl", "value": "Timed urine collection"},
{"key": "ucth", "value": "Urine specimen collection, catheterized"},
{"key": "scgh", "value": "Collection of coughed sputum"},
{"key": "bpsy", "value": "Biopsy"},
{"key": "aspn", "value": "Aspiration"},
{"key": "excs", "value": "Excision"},
{"key": "scrp", "value": "Scraping"}
]
}
{"name": "collection_method",
"VSName": "Collection Method",
"VCategory": "System",
"values": [
{"key": "pcntr", "value": "Puncture"},
{"key": "fprk", "value": "Finger-prick sampling"},
{"key": "ucct", "value": "Urine specimen collection, clean catch"},
{"key": "utcl", "value": "Timed urine collection"},
{"key": "ucth", "value": "Urine specimen collection, catheterized"},
{"key": "scgh", "value": "Collection of coughed sputum"},
{"key": "bpsy", "value": "Biopsy"},
{"key": "aspn", "value": "Aspiration"},
{"key": "excs", "value": "Excision"},
{"key": "scrp", "value": "Scraping"}
]
}

View File

@ -1,14 +1,14 @@
{"name": "container_cap_color",
"VSName": "Container Cap Color",
"VCategory": "System",
"values": [
{"key": "PRPL", "value": "Purple"},
{"key": "RED", "value": "Red"},
{"key": "YLLW", "value": "Yellow"},
{"key": "GRN", "value": "Green"},
{"key": "PINK", "value": "Pink"},
{"key": "LBLU", "value": "Light Blue"},
{"key": "RBLU", "value": "Royal Blue"},
{"key": "GRAY", "value": "Gray"}
]
}
{"name": "container_cap_color",
"VSName": "Container Cap Color",
"VCategory": "System",
"values": [
{"key": "PRPL", "value": "Purple"},
{"key": "RED", "value": "Red"},
{"key": "YLLW", "value": "Yellow"},
{"key": "GRN", "value": "Green"},
{"key": "PINK", "value": "Pink"},
{"key": "LBLU", "value": "Light Blue"},
{"key": "RBLU", "value": "Royal Blue"},
{"key": "GRAY", "value": "Gray"}
]
}

View File

@ -1,9 +1,9 @@
{"name": "container_class",
"VSName": "Container Class",
"VCategory": "System",
"values": [
{"key": "Pri", "value": "Primary"},
{"key": "Sec", "value": "Secondary"},
{"key": "Ter", "value": "Tertiary"}
]
}
{"name": "container_class",
"VSName": "Container Class",
"VCategory": "System",
"values": [
{"key": "Pri", "value": "Primary"},
{"key": "Sec", "value": "Secondary"},
{"key": "Ter", "value": "Tertiary"}
]
}

View File

@ -1,10 +1,10 @@
{"name": "container_size",
"VSName": "Container Size",
"VCategory": "System",
"values": [
{"key": "5ml", "value": "5 mL"},
{"key": "7ml", "value": "7 mL"},
{"key": "10ml", "value": "10 mL"},
{"key": "1l", "value": "1 L"}
]
}
{"name": "container_size",
"VSName": "Container Size",
"VCategory": "System",
"values": [
{"key": "5ml", "value": "5 mL"},
{"key": "7ml", "value": "7 mL"},
{"key": "10ml", "value": "10 mL"},
{"key": "1l", "value": "1 L"}
]
}

View File

@ -1,255 +1,255 @@
{"name": "country",
"VSName": "Country",
"VCategory": "System",
"values": [
{"key": "AFG", "value": "Afghanistan"},
{"key": "ALA", "value": "Åland Islands"},
{"key": "ALB", "value": "Albania"},
{"key": "DZA", "value": "Algeria"},
{"key": "ASM", "value": "American Samoa"},
{"key": "AND", "value": "Andorra"},
{"key": "AGO", "value": "Angola"},
{"key": "AIA", "value": "Anguilla"},
{"key": "ATA", "value": "Antarctica"},
{"key": "ATG", "value": "Antigua and Barbuda"},
{"key": "ARG", "value": "Argentina"},
{"key": "ARM", "value": "Armenia"},
{"key": "ABW", "value": "Aruba"},
{"key": "AUS", "value": "Australia"},
{"key": "AUT", "value": "Austria"},
{"key": "AZE", "value": "Azerbaijan"},
{"key": "BHS", "value": "Bahamas"},
{"key": "BHR", "value": "Bahrain"},
{"key": "BGD", "value": "Bangladesh"},
{"key": "BRB", "value": "Barbados"},
{"key": "BLR", "value": "Belarus"},
{"key": "BEL", "value": "Belgium"},
{"key": "BLZ", "value": "Belize"},
{"key": "BEN", "value": "Benin"},
{"key": "BMU", "value": "Bermuda"},
{"key": "BTN", "value": "Bhutan"},
{"key": "BOL", "value": "Bolivia, Plurinational State of"},
{"key": "BES", "value": "Bonaire, Sint Eustatius and Saba"},
{"key": "BIH", "value": "Bosnia and Herzegovina"},
{"key": "BWA", "value": "Botswana"},
{"key": "BVT", "value": "Bouvet Island"},
{"key": "BRA", "value": "Brazil"},
{"key": "IOT", "value": "British Indian Ocean Territory"},
{"key": "BRN", "value": "Brunei Darussalam"},
{"key": "BGR", "value": "Bulgaria"},
{"key": "BFA", "value": "Burkina Faso"},
{"key": "BDI", "value": "Burundi"},
{"key": "CPV", "value": "Cabo Verde"},
{"key": "KHM", "value": "Cambodia"},
{"key": "CMR", "value": "Cameroon"},
{"key": "CAN", "value": "Canada"},
{"key": "CYM", "value": "Cayman Islands"},
{"key": "CAF", "value": "Central African Republic"},
{"key": "TCD", "value": "Chad"},
{"key": "CHL", "value": "Chile"},
{"key": "CHN", "value": "China"},
{"key": "CXR", "value": "Christmas Island"},
{"key": "CCK", "value": "Cocos (Keeling) Islands"},
{"key": "COL", "value": "Colombia"},
{"key": "COM", "value": "Comoros"},
{"key": "COG", "value": "Congo"},
{"key": "COD", "value": "Congo, Democratic Republic of the"},
{"key": "COK", "value": "Cook Islands"},
{"key": "CRI", "value": "Costa Rica"},
{"key": "CIV", "value": "Côte d'Ivoire"},
{"key": "HRV", "value": "Croatia"},
{"key": "CUB", "value": "Cuba"},
{"key": "CUW", "value": "Curaçao"},
{"key": "CYP", "value": "Cyprus"},
{"key": "CZE", "value": "Czechia"},
{"key": "DNK", "value": "Denmark"},
{"key": "DJI", "value": "Djibouti"},
{"key": "DMA", "value": "Dominica"},
{"key": "DOM", "value": "Dominican Republic"},
{"key": "ECU", "value": "Ecuador"},
{"key": "EGY", "value": "Egypt"},
{"key": "SLV", "value": "El Salvador"},
{"key": "GNQ", "value": "Equatorial Guinea"},
{"key": "ERI", "value": "Eritrea"},
{"key": "EST", "value": "Estonia"},
{"key": "SWZ", "value": "Eswatini"},
{"key": "ETH", "value": "Ethiopia"},
{"key": "FLK", "value": "Falkland Islands (Malvinas)"},
{"key": "FRO", "value": "Faroe Islands"},
{"key": "FJI", "value": "Fiji"},
{"key": "FIN", "value": "Finland"},
{"key": "FRA", "value": "France"},
{"key": "GUF", "value": "French Guiana"},
{"key": "PYF", "value": "French Polynesia"},
{"key": "ATF", "value": "French Southern Territories"},
{"key": "GAB", "value": "Gabon"},
{"key": "GMB", "value": "Gambia"},
{"key": "GEO", "value": "Georgia"},
{"key": "DEU", "value": "Germany"},
{"key": "GHA", "value": "Ghana"},
{"key": "GIB", "value": "Gibraltar"},
{"key": "GRC", "value": "Greece"},
{"key": "GRL", "value": "Greenland"},
{"key": "GRD", "value": "Grenada"},
{"key": "GLP", "value": "Guadeloupe"},
{"key": "GUM", "value": "Guam"},
{"key": "GTM", "value": "Guatemala"},
{"key": "GGY", "value": "Guernsey"},
{"key": "GIN", "value": "Guinea"},
{"key": "GNB", "value": "Guinea-Bissau"},
{"key": "GUY", "value": "Guyana"},
{"key": "HTI", "value": "Haiti"},
{"key": "HMD", "value": "Heard Island and McDonald Islands"},
{"key": "VAT", "value": "Holy See"},
{"key": "HND", "value": "Honduras"},
{"key": "HKG", "value": "Hong Kong"},
{"key": "HUN", "value": "Hungary"},
{"key": "ISL", "value": "Iceland"},
{"key": "IND", "value": "India"},
{"key": "IDN", "value": "Indonesia"},
{"key": "IRN", "value": "Iran, Islamic Republic of"},
{"key": "IRQ", "value": "Iraq"},
{"key": "IRL", "value": "Ireland"},
{"key": "IMN", "value": "Isle of Man"},
{"key": "ISR", "value": "Israel"},
{"key": "ITA", "value": "Italy"},
{"key": "JAM", "value": "Jamaica"},
{"key": "JPN", "value": "Japan"},
{"key": "JEY", "value": "Jersey"},
{"key": "JOR", "value": "Jordan"},
{"key": "KAZ", "value": "Kazakhstan"},
{"key": "KEN", "value": "Kenya"},
{"key": "KIR", "value": "Kiribati"},
{"key": "PRK", "value": "Korea, Democratic People's Republic of"},
{"key": "KOR", "value": "Korea, Republic of"},
{"key": "KWT", "value": "Kuwait"},
{"key": "KGZ", "value": "Kyrgyzstan"},
{"key": "LAO", "value": "Lao People's Democratic Republic"},
{"key": "LVA", "value": "Latvia"},
{"key": "LBN", "value": "Lebanon"},
{"key": "LSO", "value": "Lesotho"},
{"key": "LBR", "value": "Liberia"},
{"key": "LBY", "value": "Libya"},
{"key": "LIE", "value": "Liechtenstein"},
{"key": "LTU", "value": "Lithuania"},
{"key": "LUX", "value": "Luxembourg"},
{"key": "MAC", "value": "Macao"},
{"key": "MDG", "value": "Madagascar"},
{"key": "MWI", "value": "Malawi"},
{"key": "MYS", "value": "Malaysia"},
{"key": "MDV", "value": "Maldives"},
{"key": "MLI", "value": "Mali"},
{"key": "MLT", "value": "Malta"},
{"key": "MHL", "value": "Marshall Islands"},
{"key": "MTQ", "value": "Martinique"},
{"key": "MRT", "value": "Mauritania"},
{"key": "MUS", "value": "Mauritius"},
{"key": "MYT", "value": "Mayotte"},
{"key": "MEX", "value": "Mexico"},
{"key": "FSM", "value": "Micronesia, Federated States of"},
{"key": "MDA", "value": "Moldova, Republic of"},
{"key": "MCO", "value": "Monaco"},
{"key": "MNG", "value": "Mongolia"},
{"key": "MNE", "value": "Montenegro"},
{"key": "MSR", "value": "Montserrat"},
{"key": "MAR", "value": "Morocco"},
{"key": "MOZ", "value": "Mozambique"},
{"key": "MMR", "value": "Myanmar"},
{"key": "NAM", "value": "Namibia"},
{"key": "NRU", "value": "Nauru"},
{"key": "NPL", "value": "Nepal"},
{"key": "NLD", "value": "Netherlands, Kingdom of the"},
{"key": "NCL", "value": "New Caledonia"},
{"key": "NZL", "value": "New Zealand"},
{"key": "NIC", "value": "Nicaragua"},
{"key": "NER", "value": "Niger"},
{"key": "NGA", "value": "Nigeria"},
{"key": "NIU", "value": "Niue"},
{"key": "NFK", "value": "Norfolk Island"},
{"key": "MKD", "value": "North Macedonia"},
{"key": "MNP", "value": "Northern Mariana Islands"},
{"key": "NOR", "value": "Norway"},
{"key": "OMN", "value": "Oman"},
{"key": "PAK", "value": "Pakistan"},
{"key": "PLW", "value": "Palau"},
{"key": "PSE", "value": "Palestine, State of"},
{"key": "PAN", "value": "Panama"},
{"key": "PNG", "value": "Papua New Guinea"},
{"key": "PRY", "value": "Paraguay"},
{"key": "PER", "value": "Peru"},
{"key": "PHL", "value": "Philippines"},
{"key": "PCN", "value": "Pitcairn"},
{"key": "POL", "value": "Poland"},
{"key": "PRT", "value": "Portugal"},
{"key": "PRI", "value": "Puerto Rico"},
{"key": "QAT", "value": "Qatar"},
{"key": "REU", "value": "Réunion"},
{"key": "ROU", "value": "Romania"},
{"key": "RUS", "value": "Russian Federation"},
{"key": "RWA", "value": "Rwanda"},
{"key": "BLM", "value": "Saint Barthélemy"},
{"key": "SHN", "value": "Saint Helena, Ascension and Tristan da Cunha"},
{"key": "KNA", "value": "Saint Kitts and Nevis"},
{"key": "LCA", "value": "Saint Lucia"},
{"key": "MAF", "value": "Saint Martin (French part)"},
{"key": "SPM", "value": "Saint Pierre and Miquelon"},
{"key": "VCT", "value": "Saint Vincent and the Grenadines"},
{"key": "WSM", "value": "Samoa"},
{"key": "SMR", "value": "San Marino"},
{"key": "STP", "value": "Sao Tome and Principe"},
{"key": "SAU", "value": "Saudi Arabia"},
{"key": "SEN", "value": "Senegal"},
{"key": "SRB", "value": "Serbia"},
{"key": "SYC", "value": "Seychelles"},
{"key": "SLE", "value": "Sierra Leone"},
{"key": "SGP", "value": "Singapore"},
{"key": "SXM", "value": "Sint Maarten (Dutch part)"},
{"key": "SVK", "value": "Slovakia"},
{"key": "SVN", "value": "Slovenia"},
{"key": "SLB", "value": "Solomon Islands"},
{"key": "SOM", "value": "Somalia"},
{"key": "ZAF", "value": "South Africa"},
{"key": "SGS", "value": "South Georgia and the South Sandwich Islands"},
{"key": "SSD", "value": "South Sudan"},
{"key": "ESP", "value": "Spain"},
{"key": "LKA", "value": "Sri Lanka"},
{"key": "SDN", "value": "Sudan"},
{"key": "SUR", "value": "Suriname"},
{"key": "SJM", "value": "Svalbard and Jan Mayen"},
{"key": "SWE", "value": "Sweden"},
{"key": "CHE", "value": "Switzerland"},
{"key": "SYR", "value": "Syrian Arab Republic"},
{"key": "TWN", "value": "Taiwan, Province of China"},
{"key": "TJK", "value": "Tajikistan"},
{"key": "TZA", "value": "Tanzania, United Republic of"},
{"key": "THA", "value": "Thailand"},
{"key": "TLS", "value": "Timor-Leste"},
{"key": "TGO", "value": "Togo"},
{"key": "TKL", "value": "Tokelau"},
{"key": "TON", "value": "Tonga"},
{"key": "TTO", "value": "Trinidad and Tobago"},
{"key": "TUN", "value": "Tunisia"},
{"key": "TUR", "value": "Türkiye"},
{"key": "TKM", "value": "Turkmenistan"},
{"key": "TCA", "value": "Turks and Caicos Islands"},
{"key": "TUV", "value": "Tuvalu"},
{"key": "UGA", "value": "Uganda"},
{"key": "UKR", "value": "Ukraine"},
{"key": "ARE", "value": "United Arab Emirates"},
{"key": "GBR", "value": "United Kingdom of Great Britain and Northern Ireland"},
{"key": "USA", "value": "United States of America"},
{"key": "UMI", "value": "United States Minor Outlying Islands"},
{"key": "URY", "value": "Uruguay"},
{"key": "UZB", "value": "Uzbekistan"},
{"key": "VUT", "value": "Vanuatu"},
{"key": "VEN", "value": "Venezuela, Bolivarian Republic of"},
{"key": "VNM", "value": "Viet Nam"},
{"key": "VGB", "value": "Virgin Islands (British)"},
{"key": "VIR", "value": "Virgin Islands (U.S.)"},
{"key": "WLF", "value": "Wallis and Futuna"},
{"key": "ESH", "value": "Western Sahara"},
{"key": "YEM", "value": "Yemen"},
{"key": "ZMB", "value": "Zambia"},
{"key": "ZWE", "value": "Zimbabwe"}
]
}
{"name": "country",
"VSName": "Country",
"VCategory": "System",
"values": [
{"key": "AFG", "value": "Afghanistan"},
{"key": "ALA", "value": "Åland Islands"},
{"key": "ALB", "value": "Albania"},
{"key": "DZA", "value": "Algeria"},
{"key": "ASM", "value": "American Samoa"},
{"key": "AND", "value": "Andorra"},
{"key": "AGO", "value": "Angola"},
{"key": "AIA", "value": "Anguilla"},
{"key": "ATA", "value": "Antarctica"},
{"key": "ATG", "value": "Antigua and Barbuda"},
{"key": "ARG", "value": "Argentina"},
{"key": "ARM", "value": "Armenia"},
{"key": "ABW", "value": "Aruba"},
{"key": "AUS", "value": "Australia"},
{"key": "AUT", "value": "Austria"},
{"key": "AZE", "value": "Azerbaijan"},
{"key": "BHS", "value": "Bahamas"},
{"key": "BHR", "value": "Bahrain"},
{"key": "BGD", "value": "Bangladesh"},
{"key": "BRB", "value": "Barbados"},
{"key": "BLR", "value": "Belarus"},
{"key": "BEL", "value": "Belgium"},
{"key": "BLZ", "value": "Belize"},
{"key": "BEN", "value": "Benin"},
{"key": "BMU", "value": "Bermuda"},
{"key": "BTN", "value": "Bhutan"},
{"key": "BOL", "value": "Bolivia, Plurinational State of"},
{"key": "BES", "value": "Bonaire, Sint Eustatius and Saba"},
{"key": "BIH", "value": "Bosnia and Herzegovina"},
{"key": "BWA", "value": "Botswana"},
{"key": "BVT", "value": "Bouvet Island"},
{"key": "BRA", "value": "Brazil"},
{"key": "IOT", "value": "British Indian Ocean Territory"},
{"key": "BRN", "value": "Brunei Darussalam"},
{"key": "BGR", "value": "Bulgaria"},
{"key": "BFA", "value": "Burkina Faso"},
{"key": "BDI", "value": "Burundi"},
{"key": "CPV", "value": "Cabo Verde"},
{"key": "KHM", "value": "Cambodia"},
{"key": "CMR", "value": "Cameroon"},
{"key": "CAN", "value": "Canada"},
{"key": "CYM", "value": "Cayman Islands"},
{"key": "CAF", "value": "Central African Republic"},
{"key": "TCD", "value": "Chad"},
{"key": "CHL", "value": "Chile"},
{"key": "CHN", "value": "China"},
{"key": "CXR", "value": "Christmas Island"},
{"key": "CCK", "value": "Cocos (Keeling) Islands"},
{"key": "COL", "value": "Colombia"},
{"key": "COM", "value": "Comoros"},
{"key": "COG", "value": "Congo"},
{"key": "COD", "value": "Congo, Democratic Republic of the"},
{"key": "COK", "value": "Cook Islands"},
{"key": "CRI", "value": "Costa Rica"},
{"key": "CIV", "value": "Côte d'Ivoire"},
{"key": "HRV", "value": "Croatia"},
{"key": "CUB", "value": "Cuba"},
{"key": "CUW", "value": "Curaçao"},
{"key": "CYP", "value": "Cyprus"},
{"key": "CZE", "value": "Czechia"},
{"key": "DNK", "value": "Denmark"},
{"key": "DJI", "value": "Djibouti"},
{"key": "DMA", "value": "Dominica"},
{"key": "DOM", "value": "Dominican Republic"},
{"key": "ECU", "value": "Ecuador"},
{"key": "EGY", "value": "Egypt"},
{"key": "SLV", "value": "El Salvador"},
{"key": "GNQ", "value": "Equatorial Guinea"},
{"key": "ERI", "value": "Eritrea"},
{"key": "EST", "value": "Estonia"},
{"key": "SWZ", "value": "Eswatini"},
{"key": "ETH", "value": "Ethiopia"},
{"key": "FLK", "value": "Falkland Islands (Malvinas)"},
{"key": "FRO", "value": "Faroe Islands"},
{"key": "FJI", "value": "Fiji"},
{"key": "FIN", "value": "Finland"},
{"key": "FRA", "value": "France"},
{"key": "GUF", "value": "French Guiana"},
{"key": "PYF", "value": "French Polynesia"},
{"key": "ATF", "value": "French Southern Territories"},
{"key": "GAB", "value": "Gabon"},
{"key": "GMB", "value": "Gambia"},
{"key": "GEO", "value": "Georgia"},
{"key": "DEU", "value": "Germany"},
{"key": "GHA", "value": "Ghana"},
{"key": "GIB", "value": "Gibraltar"},
{"key": "GRC", "value": "Greece"},
{"key": "GRL", "value": "Greenland"},
{"key": "GRD", "value": "Grenada"},
{"key": "GLP", "value": "Guadeloupe"},
{"key": "GUM", "value": "Guam"},
{"key": "GTM", "value": "Guatemala"},
{"key": "GGY", "value": "Guernsey"},
{"key": "GIN", "value": "Guinea"},
{"key": "GNB", "value": "Guinea-Bissau"},
{"key": "GUY", "value": "Guyana"},
{"key": "HTI", "value": "Haiti"},
{"key": "HMD", "value": "Heard Island and McDonald Islands"},
{"key": "VAT", "value": "Holy See"},
{"key": "HND", "value": "Honduras"},
{"key": "HKG", "value": "Hong Kong"},
{"key": "HUN", "value": "Hungary"},
{"key": "ISL", "value": "Iceland"},
{"key": "IND", "value": "India"},
{"key": "IDN", "value": "Indonesia"},
{"key": "IRN", "value": "Iran, Islamic Republic of"},
{"key": "IRQ", "value": "Iraq"},
{"key": "IRL", "value": "Ireland"},
{"key": "IMN", "value": "Isle of Man"},
{"key": "ISR", "value": "Israel"},
{"key": "ITA", "value": "Italy"},
{"key": "JAM", "value": "Jamaica"},
{"key": "JPN", "value": "Japan"},
{"key": "JEY", "value": "Jersey"},
{"key": "JOR", "value": "Jordan"},
{"key": "KAZ", "value": "Kazakhstan"},
{"key": "KEN", "value": "Kenya"},
{"key": "KIR", "value": "Kiribati"},
{"key": "PRK", "value": "Korea, Democratic People's Republic of"},
{"key": "KOR", "value": "Korea, Republic of"},
{"key": "KWT", "value": "Kuwait"},
{"key": "KGZ", "value": "Kyrgyzstan"},
{"key": "LAO", "value": "Lao People's Democratic Republic"},
{"key": "LVA", "value": "Latvia"},
{"key": "LBN", "value": "Lebanon"},
{"key": "LSO", "value": "Lesotho"},
{"key": "LBR", "value": "Liberia"},
{"key": "LBY", "value": "Libya"},
{"key": "LIE", "value": "Liechtenstein"},
{"key": "LTU", "value": "Lithuania"},
{"key": "LUX", "value": "Luxembourg"},
{"key": "MAC", "value": "Macao"},
{"key": "MDG", "value": "Madagascar"},
{"key": "MWI", "value": "Malawi"},
{"key": "MYS", "value": "Malaysia"},
{"key": "MDV", "value": "Maldives"},
{"key": "MLI", "value": "Mali"},
{"key": "MLT", "value": "Malta"},
{"key": "MHL", "value": "Marshall Islands"},
{"key": "MTQ", "value": "Martinique"},
{"key": "MRT", "value": "Mauritania"},
{"key": "MUS", "value": "Mauritius"},
{"key": "MYT", "value": "Mayotte"},
{"key": "MEX", "value": "Mexico"},
{"key": "FSM", "value": "Micronesia, Federated States of"},
{"key": "MDA", "value": "Moldova, Republic of"},
{"key": "MCO", "value": "Monaco"},
{"key": "MNG", "value": "Mongolia"},
{"key": "MNE", "value": "Montenegro"},
{"key": "MSR", "value": "Montserrat"},
{"key": "MAR", "value": "Morocco"},
{"key": "MOZ", "value": "Mozambique"},
{"key": "MMR", "value": "Myanmar"},
{"key": "NAM", "value": "Namibia"},
{"key": "NRU", "value": "Nauru"},
{"key": "NPL", "value": "Nepal"},
{"key": "NLD", "value": "Netherlands, Kingdom of the"},
{"key": "NCL", "value": "New Caledonia"},
{"key": "NZL", "value": "New Zealand"},
{"key": "NIC", "value": "Nicaragua"},
{"key": "NER", "value": "Niger"},
{"key": "NGA", "value": "Nigeria"},
{"key": "NIU", "value": "Niue"},
{"key": "NFK", "value": "Norfolk Island"},
{"key": "MKD", "value": "North Macedonia"},
{"key": "MNP", "value": "Northern Mariana Islands"},
{"key": "NOR", "value": "Norway"},
{"key": "OMN", "value": "Oman"},
{"key": "PAK", "value": "Pakistan"},
{"key": "PLW", "value": "Palau"},
{"key": "PSE", "value": "Palestine, State of"},
{"key": "PAN", "value": "Panama"},
{"key": "PNG", "value": "Papua New Guinea"},
{"key": "PRY", "value": "Paraguay"},
{"key": "PER", "value": "Peru"},
{"key": "PHL", "value": "Philippines"},
{"key": "PCN", "value": "Pitcairn"},
{"key": "POL", "value": "Poland"},
{"key": "PRT", "value": "Portugal"},
{"key": "PRI", "value": "Puerto Rico"},
{"key": "QAT", "value": "Qatar"},
{"key": "REU", "value": "Réunion"},
{"key": "ROU", "value": "Romania"},
{"key": "RUS", "value": "Russian Federation"},
{"key": "RWA", "value": "Rwanda"},
{"key": "BLM", "value": "Saint Barthélemy"},
{"key": "SHN", "value": "Saint Helena, Ascension and Tristan da Cunha"},
{"key": "KNA", "value": "Saint Kitts and Nevis"},
{"key": "LCA", "value": "Saint Lucia"},
{"key": "MAF", "value": "Saint Martin (French part)"},
{"key": "SPM", "value": "Saint Pierre and Miquelon"},
{"key": "VCT", "value": "Saint Vincent and the Grenadines"},
{"key": "WSM", "value": "Samoa"},
{"key": "SMR", "value": "San Marino"},
{"key": "STP", "value": "Sao Tome and Principe"},
{"key": "SAU", "value": "Saudi Arabia"},
{"key": "SEN", "value": "Senegal"},
{"key": "SRB", "value": "Serbia"},
{"key": "SYC", "value": "Seychelles"},
{"key": "SLE", "value": "Sierra Leone"},
{"key": "SGP", "value": "Singapore"},
{"key": "SXM", "value": "Sint Maarten (Dutch part)"},
{"key": "SVK", "value": "Slovakia"},
{"key": "SVN", "value": "Slovenia"},
{"key": "SLB", "value": "Solomon Islands"},
{"key": "SOM", "value": "Somalia"},
{"key": "ZAF", "value": "South Africa"},
{"key": "SGS", "value": "South Georgia and the South Sandwich Islands"},
{"key": "SSD", "value": "South Sudan"},
{"key": "ESP", "value": "Spain"},
{"key": "LKA", "value": "Sri Lanka"},
{"key": "SDN", "value": "Sudan"},
{"key": "SUR", "value": "Suriname"},
{"key": "SJM", "value": "Svalbard and Jan Mayen"},
{"key": "SWE", "value": "Sweden"},
{"key": "CHE", "value": "Switzerland"},
{"key": "SYR", "value": "Syrian Arab Republic"},
{"key": "TWN", "value": "Taiwan, Province of China"},
{"key": "TJK", "value": "Tajikistan"},
{"key": "TZA", "value": "Tanzania, United Republic of"},
{"key": "THA", "value": "Thailand"},
{"key": "TLS", "value": "Timor-Leste"},
{"key": "TGO", "value": "Togo"},
{"key": "TKL", "value": "Tokelau"},
{"key": "TON", "value": "Tonga"},
{"key": "TTO", "value": "Trinidad and Tobago"},
{"key": "TUN", "value": "Tunisia"},
{"key": "TUR", "value": "Türkiye"},
{"key": "TKM", "value": "Turkmenistan"},
{"key": "TCA", "value": "Turks and Caicos Islands"},
{"key": "TUV", "value": "Tuvalu"},
{"key": "UGA", "value": "Uganda"},
{"key": "UKR", "value": "Ukraine"},
{"key": "ARE", "value": "United Arab Emirates"},
{"key": "GBR", "value": "United Kingdom of Great Britain and Northern Ireland"},
{"key": "USA", "value": "United States of America"},
{"key": "UMI", "value": "United States Minor Outlying Islands"},
{"key": "URY", "value": "Uruguay"},
{"key": "UZB", "value": "Uzbekistan"},
{"key": "VUT", "value": "Vanuatu"},
{"key": "VEN", "value": "Venezuela, Bolivarian Republic of"},
{"key": "VNM", "value": "Viet Nam"},
{"key": "VGB", "value": "Virgin Islands (British)"},
{"key": "VIR", "value": "Virgin Islands (U.S.)"},
{"key": "WLF", "value": "Wallis and Futuna"},
{"key": "ESH", "value": "Western Sahara"},
{"key": "YEM", "value": "Yemen"},
{"key": "ZMB", "value": "Zambia"},
{"key": "ZWE", "value": "Zimbabwe"}
]
}

View File

@ -1,14 +1,14 @@
{"name": "ethnic",
"VSName": "Ethnic",
"VCategory": "System",
"values": [
{"key": "PPMLN", "value": "Papua Melanezoid"},
{"key": "NGRID", "value": "Negroid"},
{"key": "WDOID", "value": "Weddoid"},
{"key": "MMPM", "value": "Melayu Mongoloid_Proto Melayu"},
{"key": "MMDM", "value": "Melayu Mongoloid_Deutro Melayu"},
{"key": "TNGHA", "value": "Tionghoa"},
{"key": "INDIA", "value": "India"},
{"key": "ARAB", "value": "Arab"}
]
}
{"name": "ethnic",
"VSName": "Ethnic",
"VCategory": "System",
"values": [
{"key": "PPMLN", "value": "Papua Melanezoid"},
{"key": "NGRID", "value": "Negroid"},
{"key": "WDOID", "value": "Weddoid"},
{"key": "MMPM", "value": "Melayu Mongoloid_Proto Melayu"},
{"key": "MMDM", "value": "Melayu Mongoloid_Deutro Melayu"},
{"key": "TNGHA", "value": "Tionghoa"},
{"key": "INDIA", "value": "India"},
{"key": "ARAB", "value": "Arab"}
]
}

View File

@ -1,79 +1,79 @@
{
"name": "event_id",
"VSName": "Audit Event ID",
"VCategory": "System",
"values": [
{"key": "PATIENT_REGISTERED", "value": "Patient registered"},
{"key": "PATIENT_DEMOGRAPHICS_UPDATED", "value": "Patient demographics updated"},
{"key": "PATIENT_MERGED", "value": "Patient merged"},
{"key": "PATIENT_UNMERGED", "value": "Patient unmerged"},
{"key": "PATIENT_IDENTIFIER_UPDATED", "value": "Patient identifier updated"},
{"key": "PATIENT_CONSENT_UPDATED", "value": "Patient consent updated"},
{"key": "PATIENT_INSURANCE_UPDATED", "value": "Patient insurance updated"},
{"key": "PATIENT_DELETED", "value": "Patient deleted"},
{"key": "VISIT_ADMITTED", "value": "Visit admitted"},
{"key": "VISIT_TRANSFERRED", "value": "Visit transferred"},
{"key": "VISIT_DISCHARGED", "value": "Visit discharged"},
{"key": "VISIT_STATUS_UPDATED", "value": "Visit status updated"},
{"key": "ORDER_CREATED", "value": "Order created"},
{"key": "ORDER_CANCELLED", "value": "Order cancelled"},
{"key": "ORDER_REOPENED", "value": "Order reopened"},
{"key": "ORDER_TEST_ADDED", "value": "Order test added"},
{"key": "ORDER_TEST_REMOVED", "value": "Order test removed"},
{"key": "SPECIMEN_COLLECTED", "value": "Specimen collected"},
{"key": "SPECIMEN_RECEIVED", "value": "Specimen received"},
{"key": "SPECIMEN_REJECTED", "value": "Specimen rejected"},
{"key": "SPECIMEN_ALIQUOTED", "value": "Specimen aliquoted"},
{"key": "SPECIMEN_DISPOSED", "value": "Specimen disposed"},
{"key": "RESULT_ENTERED", "value": "Result entered"},
{"key": "RESULT_UPDATED", "value": "Result updated"},
{"key": "RESULT_VERIFIED", "value": "Result verified"},
{"key": "RESULT_AMENDED", "value": "Result amended"},
{"key": "RESULT_RELEASED", "value": "Result released"},
{"key": "RESULT_RETRACTED", "value": "Result retracted"},
{"key": "RESULT_CORRECTED", "value": "Result corrected"},
{"key": "QC_RECORDED", "value": "QC recorded"},
{"key": "QC_FAILED", "value": "QC failed"},
{"key": "QC_OVERRIDE_APPLIED", "value": "QC override applied"},
{"key": "VALUESET_ITEM_CREATED", "value": "Value set item created"},
{"key": "VALUESET_ITEM_UPDATED", "value": "Value set item updated"},
{"key": "VALUESET_ITEM_RETIRED", "value": "Value set item retired"},
{"key": "TEST_DEFINITION_UPDATED", "value": "Test definition updated"},
{"key": "REFERENCE_RANGE_UPDATED", "value": "Reference range updated"},
{"key": "TEST_PANEL_MEMBERSHIP_UPDATED", "value": "Test panel membership updated"},
{"key": "ANALYZER_CONFIG_UPDATED", "value": "Analyzer config updated"},
{"key": "INTEGRATION_CONFIG_UPDATED", "value": "Integration config updated"},
{"key": "CODING_SYSTEM_UPDATED", "value": "Coding system updated"},
{"key": "USER_CREATED", "value": "User created"},
{"key": "USER_DISABLED", "value": "User disabled"},
{"key": "USER_PASSWORD_RESET", "value": "User password reset"},
{"key": "USER_ROLE_CHANGED", "value": "User role changed"},
{"key": "USER_PERMISSION_CHANGED", "value": "User permission changed"},
{"key": "SITE_CREATED", "value": "Site created"},
{"key": "SITE_UPDATED", "value": "Site updated"},
{"key": "WORKSTATION_UPDATED", "value": "Workstation updated"},
{"key": "AUTH_LOGIN_SUCCESS", "value": "Auth login success"},
{"key": "AUTH_LOGOUT_SUCCESS", "value": "Auth logout success"},
{"key": "AUTH_LOGIN_FAILED", "value": "Auth login failed"},
{"key": "AUTH_LOCKOUT_TRIGGERED", "value": "Auth lockout triggered"},
{"key": "TOKEN_ISSUED", "value": "Token issued"},
{"key": "TOKEN_REFRESHED", "value": "Token refreshed"},
{"key": "TOKEN_REVOKED", "value": "Token revoked"},
{"key": "AUTHORIZATION_FAILED", "value": "Authorization failed"},
{"key": "IMPORT_JOB_STARTED", "value": "Import job started"},
{"key": "IMPORT_JOB_FINISHED", "value": "Import job finished"},
{"key": "EXPORT_JOB_STARTED", "value": "Export job started"},
{"key": "EXPORT_JOB_FINISHED", "value": "Export job finished"},
{"key": "JOB_STARTED", "value": "Job started"},
{"key": "JOB_FINISHED", "value": "Job finished"},
{"key": "INTEGRATION_SYNC_STARTED", "value": "Integration sync started"},
{"key": "INTEGRATION_SYNC_FINISHED", "value": "Integration sync finished"},
{"key": "AUDIT_WRITE_FAILED", "value": "Audit write failed"},
{"key": "AUDIT_ARCHIVE_EXECUTED", "value": "Audit archive executed"},
{"key": "AUDIT_PURGE_EXECUTED", "value": "Audit purge executed"},
{"key": "AUDIT_CHECKSUM_CREATED", "value": "Audit checksum created"},
{"key": "AUDIT_CHECKSUM_FAILED", "value": "Audit checksum failed"},
{"key": "LEGAL_HOLD_APPLIED", "value": "Legal hold applied"},
{"key": "LEGAL_HOLD_RELEASED", "value": "Legal hold released"}
]
}
{
"name": "event_id",
"VSName": "Audit Event ID",
"VCategory": "System",
"values": [
{"key": "PATIENT_REGISTERED", "value": "Patient registered"},
{"key": "PATIENT_DEMOGRAPHICS_UPDATED", "value": "Patient demographics updated"},
{"key": "PATIENT_MERGED", "value": "Patient merged"},
{"key": "PATIENT_UNMERGED", "value": "Patient unmerged"},
{"key": "PATIENT_IDENTIFIER_UPDATED", "value": "Patient identifier updated"},
{"key": "PATIENT_CONSENT_UPDATED", "value": "Patient consent updated"},
{"key": "PATIENT_INSURANCE_UPDATED", "value": "Patient insurance updated"},
{"key": "PATIENT_DELETED", "value": "Patient deleted"},
{"key": "VISIT_ADMITTED", "value": "Visit admitted"},
{"key": "VISIT_TRANSFERRED", "value": "Visit transferred"},
{"key": "VISIT_DISCHARGED", "value": "Visit discharged"},
{"key": "VISIT_STATUS_UPDATED", "value": "Visit status updated"},
{"key": "ORDER_CREATED", "value": "Order created"},
{"key": "ORDER_CANCELLED", "value": "Order cancelled"},
{"key": "ORDER_REOPENED", "value": "Order reopened"},
{"key": "ORDER_TEST_ADDED", "value": "Order test added"},
{"key": "ORDER_TEST_REMOVED", "value": "Order test removed"},
{"key": "SPECIMEN_COLLECTED", "value": "Specimen collected"},
{"key": "SPECIMEN_RECEIVED", "value": "Specimen received"},
{"key": "SPECIMEN_REJECTED", "value": "Specimen rejected"},
{"key": "SPECIMEN_ALIQUOTED", "value": "Specimen aliquoted"},
{"key": "SPECIMEN_DISPOSED", "value": "Specimen disposed"},
{"key": "RESULT_ENTERED", "value": "Result entered"},
{"key": "RESULT_UPDATED", "value": "Result updated"},
{"key": "RESULT_VERIFIED", "value": "Result verified"},
{"key": "RESULT_AMENDED", "value": "Result amended"},
{"key": "RESULT_RELEASED", "value": "Result released"},
{"key": "RESULT_RETRACTED", "value": "Result retracted"},
{"key": "RESULT_CORRECTED", "value": "Result corrected"},
{"key": "QC_RECORDED", "value": "QC recorded"},
{"key": "QC_FAILED", "value": "QC failed"},
{"key": "QC_OVERRIDE_APPLIED", "value": "QC override applied"},
{"key": "VALUESET_ITEM_CREATED", "value": "Value set item created"},
{"key": "VALUESET_ITEM_UPDATED", "value": "Value set item updated"},
{"key": "VALUESET_ITEM_RETIRED", "value": "Value set item retired"},
{"key": "TEST_DEFINITION_UPDATED", "value": "Test definition updated"},
{"key": "REFERENCE_RANGE_UPDATED", "value": "Reference range updated"},
{"key": "TEST_PANEL_MEMBERSHIP_UPDATED", "value": "Test panel membership updated"},
{"key": "ANALYZER_CONFIG_UPDATED", "value": "Analyzer config updated"},
{"key": "INTEGRATION_CONFIG_UPDATED", "value": "Integration config updated"},
{"key": "CODING_SYSTEM_UPDATED", "value": "Coding system updated"},
{"key": "USER_CREATED", "value": "User created"},
{"key": "USER_DISABLED", "value": "User disabled"},
{"key": "USER_PASSWORD_RESET", "value": "User password reset"},
{"key": "USER_ROLE_CHANGED", "value": "User role changed"},
{"key": "USER_PERMISSION_CHANGED", "value": "User permission changed"},
{"key": "SITE_CREATED", "value": "Site created"},
{"key": "SITE_UPDATED", "value": "Site updated"},
{"key": "WORKSTATION_UPDATED", "value": "Workstation updated"},
{"key": "AUTH_LOGIN_SUCCESS", "value": "Auth login success"},
{"key": "AUTH_LOGOUT_SUCCESS", "value": "Auth logout success"},
{"key": "AUTH_LOGIN_FAILED", "value": "Auth login failed"},
{"key": "AUTH_LOCKOUT_TRIGGERED", "value": "Auth lockout triggered"},
{"key": "TOKEN_ISSUED", "value": "Token issued"},
{"key": "TOKEN_REFRESHED", "value": "Token refreshed"},
{"key": "TOKEN_REVOKED", "value": "Token revoked"},
{"key": "AUTHORIZATION_FAILED", "value": "Authorization failed"},
{"key": "IMPORT_JOB_STARTED", "value": "Import job started"},
{"key": "IMPORT_JOB_FINISHED", "value": "Import job finished"},
{"key": "EXPORT_JOB_STARTED", "value": "Export job started"},
{"key": "EXPORT_JOB_FINISHED", "value": "Export job finished"},
{"key": "JOB_STARTED", "value": "Job started"},
{"key": "JOB_FINISHED", "value": "Job finished"},
{"key": "INTEGRATION_SYNC_STARTED", "value": "Integration sync started"},
{"key": "INTEGRATION_SYNC_FINISHED", "value": "Integration sync finished"},
{"key": "AUDIT_WRITE_FAILED", "value": "Audit write failed"},
{"key": "AUDIT_ARCHIVE_EXECUTED", "value": "Audit archive executed"},
{"key": "AUDIT_PURGE_EXECUTED", "value": "Audit purge executed"},
{"key": "AUDIT_CHECKSUM_CREATED", "value": "Audit checksum created"},
{"key": "AUDIT_CHECKSUM_FAILED", "value": "Audit checksum failed"},
{"key": "LEGAL_HOLD_APPLIED", "value": "Legal hold applied"},
{"key": "LEGAL_HOLD_RELEASED", "value": "Legal hold released"}
]
}

View File

@ -1,9 +1,9 @@
{"name": "fasting_status",
"VSName": "Fasting Status",
"VCategory": "System",
"values": [
{"key": "F", "value": "Fasting"},
{"key": "NF", "value": "Not Fasting"},
{"key": "NG", "value": "Not Given"}
]
}
{"name": "fasting_status",
"VSName": "Fasting Status",
"VCategory": "System",
"values": [
{"key": "F", "value": "Fasting"},
{"key": "NF", "value": "Not Fasting"},
{"key": "NG", "value": "Not Given"}
]
}

View File

@ -1,10 +1,10 @@
{"name": "formula_language",
"VSName": "Formula Language",
"VCategory": "System",
"values": [
{"key": "Phyton", "value": "Phyton"},
{"key": "CQL", "value": "Clinical Quality Language"},
{"key": "FHIRP", "value": "FHIRPath"},
{"key": "SQL", "value": "SQL"}
]
}
{"name": "formula_language",
"VSName": "Formula Language",
"VCategory": "System",
"values": [
{"key": "Phyton", "value": "Phyton"},
{"key": "CQL", "value": "Clinical Quality Language"},
{"key": "FHIRP", "value": "FHIRPath"},
{"key": "SQL", "value": "SQL"}
]
}

View File

@ -1,11 +1,11 @@
{"name": "identifier_type",
"VSName": "Identifier Type",
"VCategory": "System",
"values": [
{"key": "KTP", "value": "Kartu Tanda Penduduk"},
{"key": "PASS", "value": "Passport"},
{"key": "SSN", "value": "Social Security Number"},
{"key": "SIM", "value": "Surat Izin Mengemudi"},
{"key": "KTAS", "value": "Kartu Izin Tinggal Terbatas"}
]
}
{"name": "identifier_type",
"VSName": "Identifier Type",
"VCategory": "System",
"values": [
{"key": "KTP", "value": "Kartu Tanda Penduduk"},
{"key": "PASS", "value": "Passport"},
{"key": "SSN", "value": "Social Security Number"},
{"key": "SIM", "value": "Surat Izin Mengemudi"},
{"key": "KTAS", "value": "Kartu Izin Tinggal Terbatas"}
]
}

View File

@ -1,14 +1,14 @@
{"name": "location_type",
"VSName": "Location Type",
"VCategory": "System",
"values": [
{"key": "FCLT", "value": "Facility"},
{"key": "BLDG", "value": "Building"},
{"key": "FLOR", "value": "Floor"},
{"key": "POC", "value": "Point of Care"},
{"key": "ROOM", "value": "Room"},
{"key": "BED", "value": "Bed"},
{"key": "MOBL", "value": "Mobile"},
{"key": "REMT", "value": "Remote"}
]
}
{"name": "location_type",
"VSName": "Location Type",
"VCategory": "System",
"values": [
{"key": "FCLT", "value": "Facility"},
{"key": "BLDG", "value": "Building"},
{"key": "FLOR", "value": "Floor"},
{"key": "POC", "value": "Point of Care"},
{"key": "ROOM", "value": "Room"},
{"key": "BED", "value": "Bed"},
{"key": "MOBL", "value": "Mobile"},
{"key": "REMT", "value": "Remote"}
]
}

View File

@ -1,14 +1,14 @@
{"name": "marital_status",
"VSName": "Marital Status",
"VCategory": "System",
"values": [
{"key": "A", "value": "Separated"},
{"key": "D", "value": "Divorced"},
{"key": "M", "value": "Married"},
{"key": "S", "value": "Single"},
{"key": "W", "value": "Widowed"},
{"key": "B", "value": "Unmarried"},
{"key": "U", "value": "Unknown"},
{"key": "O", "value": "Other"}
]
}
{"name": "marital_status",
"VSName": "Marital Status",
"VCategory": "System",
"values": [
{"key": "A", "value": "Separated"},
{"key": "D", "value": "Divorced"},
{"key": "M", "value": "Married"},
{"key": "S", "value": "Single"},
{"key": "W", "value": "Widowed"},
{"key": "B", "value": "Unmarried"},
{"key": "U", "value": "Unknown"},
{"key": "O", "value": "Other"}
]
}

View File

@ -1,11 +1,11 @@
{"name": "math_sign",
"VSName": "Math Sign",
"VCategory": "System",
"values": [
{"key": "=", "value": "Equal"},
{"key": "<", "value": "Less than"},
{"key": ">", "value": "Greater than"},
{"key": "<=", "value": "Less than or equal to"},
{"key": ">=", "value": "Greater than or equal to"}
]
}
{"name": "math_sign",
"VSName": "Math Sign",
"VCategory": "System",
"values": [
{"key": "=", "value": "Equal"},
{"key": "<", "value": "Less than"},
{"key": ">", "value": "Greater than"},
{"key": "<=", "value": "Less than or equal to"},
{"key": ">=", "value": "Greater than or equal to"}
]
}

View File

@ -1,8 +1,8 @@
{"name": "numeric_ref_type",
"VSName": "Numeric Reference Type",
"VCategory": "System",
"values": [
{"key": "RANGE", "value": "Range"},
{"key": "THOLD", "value": "Threshold"}
]
}
{"name": "numeric_ref_type",
"VSName": "Numeric Reference Type",
"VCategory": "System",
"values": [
{"key": "RANGE", "value": "Range"},
{"key": "THOLD", "value": "Threshold"}
]
}

View File

@ -1,37 +1,37 @@
{"name": "race",
"VSName": "Race (Ethnicity)",
"VCategory": "System",
"values": [
{"key": "JAWA", "value": "Jawa"},
{"key": "SUNDA", "value": "Sunda"},
{"key": "BATAK", "value": "Batak"},
{"key": "SULOR", "value": "Suku asal Sulawesi lainnya"},
{"key": "MDRA", "value": "Madura"},
{"key": "BTWI", "value": "Betawi"},
{"key": "MNG", "value": "Minangkabau"},
{"key": "BUGIS", "value": "Bugis"},
{"key": "MLYU", "value": "Melayu"},
{"key": "SUMSL", "value": "Suku asal Sumatera Selatan"},
{"key": "BTNOR", "value": "Suku asal Banten"},
{"key": "NTTOR", "value": "Suku asal Nusa Tenggara Timur"},
{"key": "BNJAR", "value": "Banjar"},
{"key": "ACEH", "value": "Aceh"},
{"key": "BALI", "value": "Bali"},
{"key": "SASAK", "value": "Sasak"},
{"key": "DAYAK", "value": "Dayak"},
{"key": "TNGHA", "value": "Tionghoa"},
{"key": "PPAOR", "value": "Suku asal Papua"},
{"key": "MKSSR", "value": "Makassar"},
{"key": "SUMOR", "value": "Suku asal Sumatera lainnya"},
{"key": "MLKOR", "value": "Suku asal Maluku"},
{"key": "KLMOR", "value": "Suku asal Kalimantan lainnya"},
{"key": "CRBON", "value": "Cirebon"},
{"key": "JBIOR", "value": "Suku asal Jambi"},
{"key": "LPGOR", "value": "Suku Lampung"},
{"key": "NTBOR", "value": "Suku asal Nusa Tenggara Barat lainnya"},
{"key": "GRTLO", "value": "Gorontalo"},
{"key": "MNHSA", "value": "Minahasa"},
{"key": "NIAS", "value": "Nias"},
{"key": "FORGN", "value": "Asing/luar negeri"}
]
}
{"name": "race",
"VSName": "Race (Ethnicity)",
"VCategory": "System",
"values": [
{"key": "JAWA", "value": "Jawa"},
{"key": "SUNDA", "value": "Sunda"},
{"key": "BATAK", "value": "Batak"},
{"key": "SULOR", "value": "Suku asal Sulawesi lainnya"},
{"key": "MDRA", "value": "Madura"},
{"key": "BTWI", "value": "Betawi"},
{"key": "MNG", "value": "Minangkabau"},
{"key": "BUGIS", "value": "Bugis"},
{"key": "MLYU", "value": "Melayu"},
{"key": "SUMSL", "value": "Suku asal Sumatera Selatan"},
{"key": "BTNOR", "value": "Suku asal Banten"},
{"key": "NTTOR", "value": "Suku asal Nusa Tenggara Timur"},
{"key": "BNJAR", "value": "Banjar"},
{"key": "ACEH", "value": "Aceh"},
{"key": "BALI", "value": "Bali"},
{"key": "SASAK", "value": "Sasak"},
{"key": "DAYAK", "value": "Dayak"},
{"key": "TNGHA", "value": "Tionghoa"},
{"key": "PPAOR", "value": "Suku asal Papua"},
{"key": "MKSSR", "value": "Makassar"},
{"key": "SUMOR", "value": "Suku asal Sumatera lainnya"},
{"key": "MLKOR", "value": "Suku asal Maluku"},
{"key": "KLMOR", "value": "Suku asal Kalimantan lainnya"},
{"key": "CRBON", "value": "Cirebon"},
{"key": "JBIOR", "value": "Suku asal Jambi"},
{"key": "LPGOR", "value": "Suku Lampung"},
{"key": "NTBOR", "value": "Suku asal Nusa Tenggara Barat lainnya"},
{"key": "GRTLO", "value": "Gorontalo"},
{"key": "MNHSA", "value": "Minahasa"},
{"key": "NIAS", "value": "Nias"},
{"key": "FORGN", "value": "Asing/luar negeri"}
]
}

View File

@ -1,10 +1,10 @@
{"name": "range_type",
"VSName": "Range Type",
"VCategory": "System",
"values": [
{"key": "REF", "value": "Reference Range"},
{"key": "CRTC", "value": "Critical Range"},
{"key": "VAL", "value": "Validation Range"},
{"key": "RERUN", "value": "Rerun Range"}
]
}
{"name": "range_type",
"VSName": "Range Type",
"VCategory": "System",
"values": [
{"key": "REF", "value": "Reference Range"},
{"key": "CRTC", "value": "Critical Range"},
{"key": "VAL", "value": "Validation Range"},
{"key": "RERUN", "value": "Rerun Range"}
]
}

View File

@ -1,13 +1,13 @@
{"name": "religion",
"VSName": "Religion",
"VCategory": "System",
"values": [
{"key": "ISLAM", "value": "Islam"},
{"key": "KRSTN", "value": "Kristen"},
{"key": "KTLIK", "value": "Katolik"},
{"key": "HINDU", "value": "Hindu"},
{"key": "BUDHA", "value": "Budha"},
{"key": "KHCU", "value": "Khong Hu Cu"},
{"key": "OTHER", "value": "Lainnya"}
]
}
{"name": "religion",
"VSName": "Religion",
"VCategory": "System",
"values": [
{"key": "ISLAM", "value": "Islam"},
{"key": "KRSTN", "value": "Kristen"},
{"key": "KTLIK", "value": "Katolik"},
{"key": "HINDU", "value": "Hindu"},
{"key": "BUDHA", "value": "Budha"},
{"key": "KHCU", "value": "Khong Hu Cu"},
{"key": "OTHER", "value": "Lainnya"}
]
}

View File

@ -1,26 +1,26 @@
{"name": "request_status",
"VSName": "Request Status",
"VCategory": "System",
"values": [
{"key": "STC", "value": "To be collected"},
{"key": "SCFld", "value": "Collection failed"},
{"key": "SCtd", "value": "Collected"},
{"key": "STran", "value": "In-transport"},
{"key": "STFld", "value": "Transport failed"},
{"key": "SArrv", "value": "Arrived"},
{"key": "SRejc", "value": "Rejected"},
{"key": "SRcvd", "value": "Received"},
{"key": "SPAna", "value": "Pre-analytical"},
{"key": "SPAF", "value": "Pre-analytical failed"},
{"key": "STA", "value": "To be analyze"},
{"key": "SAFld", "value": "Analytical failed"},
{"key": "SAna", "value": "Analytical"},
{"key": "STS", "value": "To be stored"},
{"key": "SSFld", "value": "Store failed"},
{"key": "SStrd", "value": "Stored"},
{"key": "SExp", "value": "Expired"},
{"key": "STD", "value": "To be destroyed"},
{"key": "SDFld", "value": "Failed to destroy"},
{"key": "SDstd", "value": "Destroyed"}
]
}
{"name": "request_status",
"VSName": "Request Status",
"VCategory": "System",
"values": [
{"key": "STC", "value": "To be collected"},
{"key": "SCFld", "value": "Collection failed"},
{"key": "SCtd", "value": "Collected"},
{"key": "STran", "value": "In-transport"},
{"key": "STFld", "value": "Transport failed"},
{"key": "SArrv", "value": "Arrived"},
{"key": "SRejc", "value": "Rejected"},
{"key": "SRcvd", "value": "Received"},
{"key": "SPAna", "value": "Pre-analytical"},
{"key": "SPAF", "value": "Pre-analytical failed"},
{"key": "STA", "value": "To be analyze"},
{"key": "SAFld", "value": "Analytical failed"},
{"key": "SAna", "value": "Analytical"},
{"key": "STS", "value": "To be stored"},
{"key": "SSFld", "value": "Store failed"},
{"key": "SStrd", "value": "Stored"},
{"key": "SExp", "value": "Expired"},
{"key": "STD", "value": "To be destroyed"},
{"key": "SDFld", "value": "Failed to destroy"},
{"key": "SDstd", "value": "Destroyed"}
]
}

View File

@ -1,10 +1,10 @@
{"name": "result_status",
"VSName": "Result Status",
"VCategory": "System",
"values": [
{"key": "PRELIMINARY", "value": "Preliminary"},
{"key": "FINAL", "value": "Final"},
{"key": "CORRECTED", "value": "Corrected"},
{"key": "CANCELLED", "value": "Cancelled"}
]
}
{"name": "result_status",
"VSName": "Result Status",
"VCategory": "System",
"values": [
{"key": "PRELIMINARY", "value": "Preliminary"},
{"key": "FINAL", "value": "Final"},
{"key": "CORRECTED", "value": "Corrected"},
{"key": "CANCELLED", "value": "Cancelled"}
]
}

View File

@ -1,16 +1,16 @@
{"name": "result_unit",
"VSName": "Result Unit",
"VCategory": "System",
"values": [
{"key": "g/dL", "value": "g/dL"},
{"key": "g/L", "value": "g/L"},
{"key": "mg/dL", "value": "mg/dL"},
{"key": "mg/L", "value": "mg/L"},
{"key": "L/L", "value": "L/L"},
{"key": "x106/mL", "value": "x106/mL"},
{"key": "x1012/L", "value": "x1012/L"},
{"key": "fL", "value": "fL"},
{"key": "pg", "value": "pg"},
{"key": "x109/L", "value": "x109/L"}
]
}
{"name": "result_unit",
"VSName": "Result Unit",
"VCategory": "System",
"values": [
{"key": "g/dL", "value": "g/dL"},
{"key": "g/L", "value": "g/L"},
{"key": "mg/dL", "value": "mg/dL"},
{"key": "mg/L", "value": "mg/L"},
{"key": "L/L", "value": "L/L"},
{"key": "x106/mL", "value": "x106/mL"},
{"key": "x1012/L", "value": "x1012/L"},
{"key": "fL", "value": "fL"},
{"key": "pg", "value": "pg"},
{"key": "x109/L", "value": "x109/L"}
]
}

View File

@ -1,12 +1,12 @@
{"name": "site_class",
"VSName": "Site Class",
"VCategory": "System",
"values": [
{"key": "A", "value": "Kelas A"},
{"key": "B", "value": "Kelas B"},
{"key": "C", "value": "Kelas C"},
{"key": "D", "value": "Kelas D"},
{"key": "Utm", "value": "Utama"},
{"key": "Ptm", "value": "Pratama"}
]
}
{"name": "site_class",
"VSName": "Site Class",
"VCategory": "System",
"values": [
{"key": "A", "value": "Kelas A"},
{"key": "B", "value": "Kelas B"},
{"key": "C", "value": "Kelas C"},
{"key": "D", "value": "Kelas D"},
{"key": "Utm", "value": "Utama"},
{"key": "Ptm", "value": "Pratama"}
]
}

View File

@ -1,12 +1,12 @@
{"name": "site_type",
"VSName": "Site Type",
"VCategory": "System",
"values": [
{"key": "GH", "value": "Government Hospital"},
{"key": "PH", "value": "Private Hospital"},
{"key": "GHL", "value": "Government Hospital Lab"},
{"key": "PHL", "value": "Private Hospital Lab"},
{"key": "GL", "value": "Government Lab"},
{"key": "PL", "value": "Private Lab"}
]
}
{"name": "site_type",
"VSName": "Site Type",
"VCategory": "System",
"values": [
{"key": "GH", "value": "Government Hospital"},
{"key": "PH", "value": "Private Hospital"},
{"key": "GHL", "value": "Government Hospital Lab"},
{"key": "PHL", "value": "Private Hospital Lab"},
{"key": "GL", "value": "Government Lab"},
{"key": "PL", "value": "Private Lab"}
]
}

View File

@ -1,13 +1,13 @@
{"name": "specimen_activity",
"VSName": "Specimen Activity",
"VCategory": "System",
"values": [
{"key": "SColl", "value": "Collection"},
{"key": "STran", "value": "Transport"},
{"key": "SRec", "value": "Reception"},
{"key": "SPrep", "value": "Preparation"},
{"key": "SAlqt", "value": "Aliquot"},
{"key": "SDisp", "value": "Dispatching"},
{"key": "SDest", "value": "Destruction"}
]
}
{"name": "specimen_activity",
"VSName": "Specimen Activity",
"VCategory": "System",
"values": [
{"key": "SColl", "value": "Collection"},
{"key": "STran", "value": "Transport"},
{"key": "SRec", "value": "Reception"},
{"key": "SPrep", "value": "Preparation"},
{"key": "SAlqt", "value": "Aliquot"},
{"key": "SDisp", "value": "Dispatching"},
{"key": "SDest", "value": "Destruction"}
]
}

View File

@ -1,17 +1,17 @@
{"name": "specimen_condition",
"VSName": "Specimen Condition",
"VCategory": "System",
"values": [
{"key": "HEM", "value": "Hemolyzed"},
{"key": "ITC", "value": "Icteric"},
{"key": "LIP", "value": "Lipemic"},
{"key": "CFU", "value": "Centrifuged"},
{"key": "ROOM", "value": "Room temperature"},
{"key": "COOL", "value": "Cool"},
{"key": "FROZ", "value": "Frozen"},
{"key": "CLOT", "value": "Clotted"},
{"key": "AUT", "value": "Autolyzed"},
{"key": "CON", "value": "Contaminated"},
{"key": "LIVE", "value": "Live"}
]
}
{"name": "specimen_condition",
"VSName": "Specimen Condition",
"VCategory": "System",
"values": [
{"key": "HEM", "value": "Hemolyzed"},
{"key": "ITC", "value": "Icteric"},
{"key": "LIP", "value": "Lipemic"},
{"key": "CFU", "value": "Centrifuged"},
{"key": "ROOM", "value": "Room temperature"},
{"key": "COOL", "value": "Cool"},
{"key": "FROZ", "value": "Frozen"},
{"key": "CLOT", "value": "Clotted"},
{"key": "AUT", "value": "Autolyzed"},
{"key": "CON", "value": "Contaminated"},
{"key": "LIVE", "value": "Live"}
]
}

View File

@ -1,15 +1,15 @@
{"name": "specimen_role",
"VSName": "Specimen Role",
"VCategory": "System",
"values": [
{"key": "P", "value": "Patient"},
{"key": "B", "value": "Blind Sample"},
{"key": "Q", "value": "Control specimen"},
{"key": "E", "value": "Electronic QC"},
{"key": "F", "value": "Filler Organization Proficiency"},
{"key": "O", "value": "Operator Proficiency"},
{"key": "C", "value": "Calibrator"},
{"key": "R", "value": "Replicate"},
{"key": "V", "value": "Verifying Calibrator"}
]
}
{"name": "specimen_role",
"VSName": "Specimen Role",
"VCategory": "System",
"values": [
{"key": "P", "value": "Patient"},
{"key": "B", "value": "Blind Sample"},
{"key": "Q", "value": "Control specimen"},
{"key": "E", "value": "Electronic QC"},
{"key": "F", "value": "Filler Organization Proficiency"},
{"key": "O", "value": "Operator Proficiency"},
{"key": "C", "value": "Calibrator"},
{"key": "R", "value": "Replicate"},
{"key": "V", "value": "Verifying Calibrator"}
]
}

View File

@ -1,26 +1,26 @@
{"name": "specimen_status",
"VSName": "Specimen Status",
"VCategory": "System",
"values": [
{"key": "STC", "value": "To be collected"},
{"key": "SCFld", "value": "Collection failed"},
{"key": "SCtd", "value": "Collected"},
{"key": "STran", "value": "In-transport"},
{"key": "STFld", "value": "Transport failed"},
{"key": "SArrv", "value": "Arrived"},
{"key": "SRejc", "value": "Rejected"},
{"key": "SRcvd", "value": "Received"},
{"key": "SPAna", "value": "Pre-analytical"},
{"key": "SPAF", "value": "Pre-analytical failed"},
{"key": "STA", "value": "To be analyze"},
{"key": "SAFld", "value": "Analytical failed"},
{"key": "SAna", "value": "Analytical"},
{"key": "STS", "value": "To be stored"},
{"key": "SSFld", "value": "Store failed"},
{"key": "SStrd", "value": "Stored"},
{"key": "SExp", "value": "Expired"},
{"key": "STD", "value": "To be destroyed"},
{"key": "SDFld", "value": "Failed to destroy"},
{"key": "SDstd", "value": "Destroyed"}
]
}
{"name": "specimen_status",
"VSName": "Specimen Status",
"VCategory": "System",
"values": [
{"key": "STC", "value": "To be collected"},
{"key": "SCFld", "value": "Collection failed"},
{"key": "SCtd", "value": "Collected"},
{"key": "STran", "value": "In-transport"},
{"key": "STFld", "value": "Transport failed"},
{"key": "SArrv", "value": "Arrived"},
{"key": "SRejc", "value": "Rejected"},
{"key": "SRcvd", "value": "Received"},
{"key": "SPAna", "value": "Pre-analytical"},
{"key": "SPAF", "value": "Pre-analytical failed"},
{"key": "STA", "value": "To be analyze"},
{"key": "SAFld", "value": "Analytical failed"},
{"key": "SAna", "value": "Analytical"},
{"key": "STS", "value": "To be stored"},
{"key": "SSFld", "value": "Store failed"},
{"key": "SStrd", "value": "Stored"},
{"key": "SExp", "value": "Expired"},
{"key": "STD", "value": "To be destroyed"},
{"key": "SDFld", "value": "Failed to destroy"},
{"key": "SDstd", "value": "Destroyed"}
]
}

View File

@ -1,21 +1,21 @@
{"name": "specimen_type",
"VSName": "Specimen Type",
"VCategory": "System",
"values": [
{"key": "BLD", "value": "Whole blood"},
{"key": "BLDA", "value": "Blood arterial"},
{"key": "BLDCO", "value": "Cord blood"},
{"key": "FBLOOD", "value": "Blood, Fetal"},
{"key": "CSF", "value": "Cerebral spinal fluid"},
{"key": "WB", "value": "Blood, Whole"},
{"key": "BBL", "value": "Blood bag"},
{"key": "SER", "value": "Serum"},
{"key": "PLAS", "value": "Plasma"},
{"key": "PLB", "value": "Plasma bag"},
{"key": "MUCOS", "value": "Mucosa"},
{"key": "MUCUS", "value": "Mucus"},
{"key": "UR", "value": "Urine"},
{"key": "RANDU", "value": "Urine, Random"},
{"key": "URINM", "value": "Urine, Midstream"}
]
}
{"name": "specimen_type",
"VSName": "Specimen Type",
"VCategory": "System",
"values": [
{"key": "BLD", "value": "Whole blood"},
{"key": "BLDA", "value": "Blood arterial"},
{"key": "BLDCO", "value": "Cord blood"},
{"key": "FBLOOD", "value": "Blood, Fetal"},
{"key": "CSF", "value": "Cerebral spinal fluid"},
{"key": "WB", "value": "Blood, Whole"},
{"key": "BBL", "value": "Blood bag"},
{"key": "SER", "value": "Serum"},
{"key": "PLAS", "value": "Plasma"},
{"key": "PLB", "value": "Plasma bag"},
{"key": "MUCOS", "value": "Mucosa"},
{"key": "MUCUS", "value": "Mucus"},
{"key": "UR", "value": "Urine"},
{"key": "RANDU", "value": "Urine, Random"},
{"key": "URINM", "value": "Urine, Midstream"}
]
}

View File

@ -1,11 +1,11 @@
{"name": "test_activity",
"VSName": "Test Activity",
"VCategory": "System",
"values": [
{"key": "ORD", "value": "Order"},
{"key": "ANA", "value": "Analyse"},
{"key": "VER", "value": "Result Verification/Technical Validation"},
{"key": "REV", "value": "Clinical Review/Clinical Validation"},
{"key": "REP", "value": "Reporting"}
]
}
{"name": "test_activity",
"VSName": "Test Activity",
"VCategory": "System",
"values": [
{"key": "ORD", "value": "Order"},
{"key": "ANA", "value": "Analyse"},
{"key": "VER", "value": "Result Verification/Technical Validation"},
{"key": "REV", "value": "Clinical Review/Clinical Validation"},
{"key": "REP", "value": "Reporting"}
]
}

View File

@ -1,10 +1,10 @@
{"name": "test_status",
"VSName": "Test Status",
"VCategory": "System",
"values": [
{"key": "PENDING", "value": "Waiting for Results"},
{"key": "IN_PROCESS", "value": "Analyzing"},
{"key": "VERIFIED", "value": "Verified & Signed"},
{"key": "REJECTED", "value": "Sample Rejected"}
]
}
{"name": "test_status",
"VSName": "Test Status",
"VCategory": "System",
"values": [
{"key": "PENDING", "value": "Waiting for Results"},
{"key": "IN_PROCESS", "value": "Analyzing"},
{"key": "VERIFIED", "value": "Verified & Signed"},
{"key": "REJECTED", "value": "Sample Rejected"}
]
}

View File

@ -1,11 +1,11 @@
{"name": "test_type",
"VSName": "Test Type",
"VCategory": "System",
"values": [
{"key": "TEST", "value": "Test"},
{"key": "PARAM", "value": "Parameter"},
{"key": "CALC", "value": "Calculated Test"},
{"key": "GROUP", "value": "Group Test"},
{"key": "TITLE", "value": "Title"}
]
}
{"name": "test_type",
"VSName": "Test Type",
"VCategory": "System",
"values": [
{"key": "TEST", "value": "Test"},
{"key": "PARAM", "value": "Parameter"},
{"key": "CALC", "value": "Calculated Test"},
{"key": "GROUP", "value": "Group Test"},
{"key": "TITLE", "value": "Title"}
]
}

View File

@ -1,8 +1,8 @@
{"name": "text_ref_type",
"VSName": "Text Reference Type",
"VCategory": "System",
"values": [
{"key": "VSET", "value": "Value Set"},
{"key": "TEXT", "value": "Text"}
]
}
{"name": "text_ref_type",
"VSName": "Text Reference Type",
"VCategory": "System",
"values": [
{"key": "VSET", "value": "Value Set"},
{"key": "TEXT", "value": "Text"}
]
}

View File

@ -1,9 +1,9 @@
{"name": "unit",
"VSName": "Unit",
"VCategory": "System",
"values": [
{"key": "L", "value": "Liter"},
{"key": "mL", "value": "Mili Liter"},
{"key": "Pcs", "value": "Pieces"}
]
}
{"name": "unit",
"VSName": "Unit",
"VCategory": "System",
"values": [
{"key": "L", "value": "Liter"},
{"key": "mL", "value": "Mili Liter"},
{"key": "Pcs", "value": "Pieces"}
]
}

View File

@ -1,9 +1,9 @@
<?php
namespace App\Models\Contact;
use App\Models\BaseModel;
<?php
namespace App\Models\Contact;
use App\Models\BaseModel;
class ContactDetailModel extends BaseModel {
protected $table = 'contactdetail';
protected $primaryKey = 'ContactDetID';
@ -18,34 +18,34 @@ class ContactDetailModel extends BaseModel {
public function syncDetails(int $ContactID, array $contactDetails) {
try {
$keptSiteIDs = [];
foreach ($contactDetails as $detail) {
if (empty($detail['SiteID'])) {
continue;
}
$detail['ContactID'] = $ContactID;
$existing = $this->where('ContactID', $ContactID)
->where('SiteID', $detail['SiteID'])
->first();
if ($existing) {
$this->update($existing[$this->primaryKey], $detail);
} else {
$this->insert($detail);
}
$keptSiteIDs[] = $detail['SiteID'];
}
// Delete missing rows
if (!empty($keptSiteIDs)) {
$this->where('ContactID', $ContactID)
->whereNotIn('SiteID', $keptSiteIDs)
->delete();
} else {
$this->where('ContactID', $ContactID)->delete();
foreach ($contactDetails as $detail) {
if (empty($detail['SiteID'])) {
continue;
}
$detail['ContactID'] = $ContactID;
$existing = $this->where('ContactID', $ContactID)
->where('SiteID', $detail['SiteID'])
->first();
if ($existing) {
$this->update($existing[$this->primaryKey], $detail);
} else {
$this->insert($detail);
}
$keptSiteIDs[] = $detail['SiteID'];
}
// Delete missing rows
if (!empty($keptSiteIDs)) {
$this->where('ContactID', $ContactID)
->whereNotIn('SiteID', $keptSiteIDs)
->delete();
} else {
$this->where('ContactID', $ContactID)->delete();
}
return [
@ -55,10 +55,10 @@ class ContactDetailModel extends BaseModel {
];
} catch (\Throwable $e) {
log_message('error', 'syncDetails error: ' . $e->getMessage());
return [
'status' => 'error',
'message' => $e->getMessage(),
return [
'status' => 'error',
'message' => $e->getMessage(),
];
}
}
@ -113,6 +113,7 @@ class ContactDetailModel extends BaseModel {
$existing = $this->where('ContactDetID', (int) $detailID)
->where('ContactID', $contactID)
->where('ContactEndDate', null)
->first();
if (empty($existing)) {
@ -122,13 +123,8 @@ class ContactDetailModel extends BaseModel {
$updateData = array_intersect_key($detail, array_flip($this->allowedFields));
unset($updateData['ContactID']);
if ($updateData !== []) {
$db = \Config\Database::connect();
$db->table($this->table)
->where('ContactDetID', (int) $detailID)
->where('ContactID', $contactID)
->where('ContactEndDate', null)
->update($updateData);
if ($updateData !== [] && !$this->update((int) $detailID, $updateData)) {
return false;
}
}

View File

@ -1,17 +1,17 @@
<?php
namespace App\Models\Organization;
use App\Models\BaseModel;
class CodingSysModel extends BaseModel {
protected $table = 'codingsys';
protected $primaryKey = 'CodingSysID';
protected $allowedFields = ['CodingSysAbb', 'FullText', 'Description', 'CreateDate', 'EndDate'];
protected $useTimestamps = true;
protected $createdField = 'CreateDate';
protected $updatedField = '';
protected $useSoftDeletes = true;
protected $deletedField = 'EndDate';
}
<?php
namespace App\Models\Organization;
use App\Models\BaseModel;
class CodingSysModel extends BaseModel {
protected $table = 'codingsys';
protected $primaryKey = 'CodingSysID';
protected $allowedFields = ['CodingSysAbb', 'FullText', 'Description', 'CreateDate', 'EndDate'];
protected $useTimestamps = true;
protected $createdField = 'CreateDate';
protected $updatedField = '';
protected $useSoftDeletes = true;
protected $deletedField = 'EndDate';
}

View File

@ -1,17 +1,17 @@
<?php
namespace App\Models\Organization;
use App\Models\BaseModel;
class HostAppModel extends BaseModel {
protected $table = 'hostapp';
protected $primaryKey = 'HostAppID';
protected $allowedFields = ['HostAppName', 'SiteID', 'CreateDate', 'EndDate'];
protected $useTimestamps = true;
protected $createdField = 'CreateDate';
protected $updatedField = '';
protected $useSoftDeletes = true;
protected $deletedField = 'EndDate';
}
<?php
namespace App\Models\Organization;
use App\Models\BaseModel;
class HostAppModel extends BaseModel {
protected $table = 'hostapp';
protected $primaryKey = 'HostAppID';
protected $allowedFields = ['HostAppName', 'SiteID', 'CreateDate', 'EndDate'];
protected $useTimestamps = true;
protected $createdField = 'CreateDate';
protected $updatedField = '';
protected $useSoftDeletes = true;
protected $deletedField = 'EndDate';
}

View File

@ -1,17 +1,17 @@
<?php
namespace App\Models\Organization;
use App\Models\BaseModel;
class HostComParaModel extends BaseModel {
protected $table = 'hostcompara';
protected $primaryKey = 'HostAppID';
protected $allowedFields = ['HostAppID', 'HostIP', 'HostPort', 'HostPwd', 'CreateDate', 'EndDate'];
protected $useTimestamps = true;
protected $createdField = 'CreateDate';
protected $updatedField = '';
protected $useSoftDeletes = true;
protected $deletedField = 'EndDate';
}
<?php
namespace App\Models\Organization;
use App\Models\BaseModel;
class HostComParaModel extends BaseModel {
protected $table = 'hostcompara';
protected $primaryKey = 'HostAppID';
protected $allowedFields = ['HostAppID', 'HostIP', 'HostPort', 'HostPwd', 'CreateDate', 'EndDate'];
protected $useTimestamps = true;
protected $createdField = 'CreateDate';
protected $updatedField = '';
protected $useSoftDeletes = true;
protected $deletedField = 'EndDate';
}

View File

@ -1,138 +1,138 @@
<?php
namespace App\Models\User;
use CodeIgniter\Model;
/**
* User Model
* Handles database operations for users
*/
class UserModel extends Model
{
protected $table = 'users';
protected $primaryKey = 'UserID';
// Allow all fields to be mass-assigned
protected $allowedFields = [
'Username',
'Email',
'Name',
'Role',
'Department',
'IsActive',
'CreatedAt',
'UpdatedAt',
'DelDate'
];
// Use timestamps (disabled, we handle manually for consistency)
protected $useTimestamps = false;
// Validation rules
protected $validationRules = [
'Username' => 'required|min_length[3]|max_length[50]',
'Email' => 'required|valid_email|max_length[100]',
];
protected $validationMessages = [
'Username' => [
'required' => 'Username is required',
'min_length' => 'Username must be at least 3 characters',
'max_length' => 'Username cannot exceed 50 characters',
],
'Email' => [
'required' => 'Email is required',
'valid_email' => 'Please provide a valid email address',
'max_length' => 'Email cannot exceed 100 characters',
],
];
/**
* Get active users only
*/
public function getActive()
{
return $this->where('DelDate', null)
->where('IsActive', true)
->findAll();
}
/**
* Find user by username
*/
public function findByUsername($username)
{
return $this->where('Username', $username)
->where('DelDate', null)
->first();
}
/**
* Find user by email
*/
public function findByEmail($email)
{
return $this->where('Email', $email)
->where('DelDate', null)
->first();
}
/**
* Search users by name, username, or email
*/
public function search($term)
{
return $this->where('DelDate', null)
->groupStart()
->like('Username', $term)
->orLike('Email', $term)
->orLike('Name', $term)
->groupEnd()
->findAll();
}
/**
* Get users by role
*/
public function getByRole($role)
{
return $this->where('Role', $role)
->where('DelDate', null)
->where('IsActive', true)
->findAll();
}
/**
* Get users by department
*/
public function getByDepartment($department)
{
return $this->where('Department', $department)
->where('DelDate', null)
->where('IsActive', true)
->findAll();
}
/**
* Soft delete user
*/
public function softDelete($id)
{
return $this->update($id, [
'DelDate' => date('Y-m-d H:i:s'),
'IsActive' => false
]);
}
/**
* Restore soft-deleted user
*/
public function restore($id)
{
return $this->update($id, [
'DelDate' => null,
'IsActive' => true
]);
}
<?php
namespace App\Models\User;
use CodeIgniter\Model;
/**
* User Model
* Handles database operations for users
*/
class UserModel extends Model
{
protected $table = 'users';
protected $primaryKey = 'UserID';
// Allow all fields to be mass-assigned
protected $allowedFields = [
'Username',
'Email',
'Name',
'Role',
'Department',
'IsActive',
'CreatedAt',
'UpdatedAt',
'DelDate'
];
// Use timestamps (disabled, we handle manually for consistency)
protected $useTimestamps = false;
// Validation rules
protected $validationRules = [
'Username' => 'required|min_length[3]|max_length[50]',
'Email' => 'required|valid_email|max_length[100]',
];
protected $validationMessages = [
'Username' => [
'required' => 'Username is required',
'min_length' => 'Username must be at least 3 characters',
'max_length' => 'Username cannot exceed 50 characters',
],
'Email' => [
'required' => 'Email is required',
'valid_email' => 'Please provide a valid email address',
'max_length' => 'Email cannot exceed 100 characters',
],
];
/**
* Get active users only
*/
public function getActive()
{
return $this->where('DelDate', null)
->where('IsActive', true)
->findAll();
}
/**
* Find user by username
*/
public function findByUsername($username)
{
return $this->where('Username', $username)
->where('DelDate', null)
->first();
}
/**
* Find user by email
*/
public function findByEmail($email)
{
return $this->where('Email', $email)
->where('DelDate', null)
->first();
}
/**
* Search users by name, username, or email
*/
public function search($term)
{
return $this->where('DelDate', null)
->groupStart()
->like('Username', $term)
->orLike('Email', $term)
->orLike('Name', $term)
->groupEnd()
->findAll();
}
/**
* Get users by role
*/
public function getByRole($role)
{
return $this->where('Role', $role)
->where('DelDate', null)
->where('IsActive', true)
->findAll();
}
/**
* Get users by department
*/
public function getByDepartment($department)
{
return $this->where('Department', $department)
->where('DelDate', null)
->where('IsActive', true)
->findAll();
}
/**
* Soft delete user
*/
public function softDelete($id)
{
return $this->update($id, [
'DelDate' => date('Y-m-d H:i:s'),
'IsActive' => false
]);
}
/**
* Restore soft-deleted user
*/
public function restore($id)
{
return $this->update($id, [
'DelDate' => null,
'IsActive' => true
]);
}
}

View File

@ -1,179 +1,179 @@
<?php
namespace App\Services;
use CodeIgniter\Database\BaseConnection;
use DateTime;
use DateTimeZone;
use InvalidArgumentException;
use Throwable;
class AuditLogService
{
private const TABLE_MAP = [
'logpatient' => 'logpatient',
'patient' => 'logpatient',
'visit' => 'logpatient',
'logorder' => 'logorder',
'order' => 'logorder',
'specimen' => 'logorder',
'result' => 'logorder',
'logmaster' => 'logmaster',
'master' => 'logmaster',
'config' => 'logmaster',
'valueset' => 'logmaster',
'logsystem' => 'logsystem',
'system' => 'logsystem',
'auth' => 'logsystem',
'job' => 'logsystem',
];
private const PRIMARY_KEYS = [
'logpatient' => 'LogPatientID',
'logorder' => 'LogOrderID',
'logmaster' => 'LogMasterID',
'logsystem' => 'LogSystemID',
];
private const DEFAULT_PAGE = 1;
private const DEFAULT_PER_PAGE = 20;
private const MAX_PER_PAGE = 100;
private static ?BaseConnection $db = null;
public function fetchLogs(array $filters): array
{
$tableKey = $filters['table'] ?? null;
if (empty($tableKey)) {
throw new InvalidArgumentException('table parameter is required');
}
$logTable = $this->resolveLogTable($tableKey);
if ($logTable === null) {
throw new InvalidArgumentException("Unknown audit table: {$tableKey}");
}
$builder = $this->getDb()->table($logTable);
$this->applyFilters($builder, $filters);
$total = (int) $builder->countAllResults(false);
$page = $this->normalizePage($filters['page'] ?? null);
$perPage = $this->normalizePerPage($filters['perPage'] ?? $filters['per_page'] ?? null);
$offset = ($page - 1) * $perPage;
$builder->orderBy('LogDate', 'DESC');
$builder->orderBy($this->getPrimaryKey($logTable), 'DESC');
$rows = $builder
->limit($perPage, $offset)
->get()
->getResultArray();
return [
'data' => $rows,
'pagination' => [
'page' => $page,
'perPage' => $perPage,
'total' => $total,
],
];
}
private function applyFilters($builder, array $filters): void
{
if (!empty($filters['rec_id'])) {
$builder->where('RecID', (string) $filters['rec_id']);
}
if (!empty($filters['event_id'])) {
$builder->where('EventID', $this->normalizeCode($filters['event_id']));
}
if (!empty($filters['activity_id'])) {
$builder->where('ActivityID', $this->normalizeCode($filters['activity_id']));
}
$this->applyDateRange($builder, $filters['from'] ?? null, $filters['to'] ?? null);
if (!empty($filters['search'])) {
$search = trim($filters['search']);
if ($search !== '') {
$builder->groupStart();
$builder->like('UserID', $search);
$builder->orLike('Reason', $search);
$builder->orLike('FldName', $search);
$builder->orLike('FldValuePrev', $search);
$builder->orLike('FldValueNew', $search);
$builder->orLike('EventID', $search);
$builder->orLike('ActivityID', $search);
$builder->groupEnd();
}
}
}
private function applyDateRange($builder, ?string $from, ?string $to): void
{
if ($from !== null && trim($from) !== '') {
$builder->where('LogDate >=', $this->normalizeDate($from));
}
if ($to !== null && trim($to) !== '') {
$builder->where('LogDate <=', $this->normalizeDate($to));
}
}
private function normalizeDate(string $value): string
{
try {
$dt = new DateTime($value, new DateTimeZone('UTC'));
} catch (Throwable $e) {
throw new InvalidArgumentException('Invalid date: ' . $value);
}
return $dt->format('Y-m-d H:i:s');
}
private function normalizeCode(string $value): string
{
return strtoupper(trim($value));
}
private function normalizePage($value): int
{
$page = (int) ($value ?? self::DEFAULT_PAGE);
return $page < 1 ? self::DEFAULT_PAGE : $page;
}
private function normalizePerPage($value): int
{
$perPage = (int) ($value ?? self::DEFAULT_PER_PAGE);
if ($perPage < 1) {
return self::DEFAULT_PER_PAGE;
}
if ($perPage > self::MAX_PER_PAGE) {
throw new InvalidArgumentException('perPage cannot be greater than ' . self::MAX_PER_PAGE);
}
return $perPage;
}
private function resolveLogTable(?string $key): ?string
{
if ($key === null) {
return null;
}
$lookup = strtolower(trim($key));
return self::TABLE_MAP[$lookup] ?? null;
}
private function getPrimaryKey(string $table): string
{
return self::PRIMARY_KEYS[$table] ?? 'LogID';
}
private function getDb(): BaseConnection
{
return self::$db ??= \Config\Database::connect();
}
}
<?php
namespace App\Services;
use CodeIgniter\Database\BaseConnection;
use DateTime;
use DateTimeZone;
use InvalidArgumentException;
use Throwable;
class AuditLogService
{
private const TABLE_MAP = [
'logpatient' => 'logpatient',
'patient' => 'logpatient',
'visit' => 'logpatient',
'logorder' => 'logorder',
'order' => 'logorder',
'specimen' => 'logorder',
'result' => 'logorder',
'logmaster' => 'logmaster',
'master' => 'logmaster',
'config' => 'logmaster',
'valueset' => 'logmaster',
'logsystem' => 'logsystem',
'system' => 'logsystem',
'auth' => 'logsystem',
'job' => 'logsystem',
];
private const PRIMARY_KEYS = [
'logpatient' => 'LogPatientID',
'logorder' => 'LogOrderID',
'logmaster' => 'LogMasterID',
'logsystem' => 'LogSystemID',
];
private const DEFAULT_PAGE = 1;
private const DEFAULT_PER_PAGE = 20;
private const MAX_PER_PAGE = 100;
private static ?BaseConnection $db = null;
public function fetchLogs(array $filters): array
{
$tableKey = $filters['table'] ?? null;
if (empty($tableKey)) {
throw new InvalidArgumentException('table parameter is required');
}
$logTable = $this->resolveLogTable($tableKey);
if ($logTable === null) {
throw new InvalidArgumentException("Unknown audit table: {$tableKey}");
}
$builder = $this->getDb()->table($logTable);
$this->applyFilters($builder, $filters);
$total = (int) $builder->countAllResults(false);
$page = $this->normalizePage($filters['page'] ?? null);
$perPage = $this->normalizePerPage($filters['perPage'] ?? $filters['per_page'] ?? null);
$offset = ($page - 1) * $perPage;
$builder->orderBy('LogDate', 'DESC');
$builder->orderBy($this->getPrimaryKey($logTable), 'DESC');
$rows = $builder
->limit($perPage, $offset)
->get()
->getResultArray();
return [
'data' => $rows,
'pagination' => [
'page' => $page,
'perPage' => $perPage,
'total' => $total,
],
];
}
private function applyFilters($builder, array $filters): void
{
if (!empty($filters['rec_id'])) {
$builder->where('RecID', (string) $filters['rec_id']);
}
if (!empty($filters['event_id'])) {
$builder->where('EventID', $this->normalizeCode($filters['event_id']));
}
if (!empty($filters['activity_id'])) {
$builder->where('ActivityID', $this->normalizeCode($filters['activity_id']));
}
$this->applyDateRange($builder, $filters['from'] ?? null, $filters['to'] ?? null);
if (!empty($filters['search'])) {
$search = trim($filters['search']);
if ($search !== '') {
$builder->groupStart();
$builder->like('UserID', $search);
$builder->orLike('Reason', $search);
$builder->orLike('FldName', $search);
$builder->orLike('FldValuePrev', $search);
$builder->orLike('FldValueNew', $search);
$builder->orLike('EventID', $search);
$builder->orLike('ActivityID', $search);
$builder->groupEnd();
}
}
}
private function applyDateRange($builder, ?string $from, ?string $to): void
{
if ($from !== null && trim($from) !== '') {
$builder->where('LogDate >=', $this->normalizeDate($from));
}
if ($to !== null && trim($to) !== '') {
$builder->where('LogDate <=', $this->normalizeDate($to));
}
}
private function normalizeDate(string $value): string
{
try {
$dt = new DateTime($value, new DateTimeZone('UTC'));
} catch (Throwable $e) {
throw new InvalidArgumentException('Invalid date: ' . $value);
}
return $dt->format('Y-m-d H:i:s');
}
private function normalizeCode(string $value): string
{
return strtoupper(trim($value));
}
private function normalizePage($value): int
{
$page = (int) ($value ?? self::DEFAULT_PAGE);
return $page < 1 ? self::DEFAULT_PAGE : $page;
}
private function normalizePerPage($value): int
{
$perPage = (int) ($value ?? self::DEFAULT_PER_PAGE);
if ($perPage < 1) {
return self::DEFAULT_PER_PAGE;
}
if ($perPage > self::MAX_PER_PAGE) {
throw new InvalidArgumentException('perPage cannot be greater than ' . self::MAX_PER_PAGE);
}
return $perPage;
}
private function resolveLogTable(?string $key): ?string
{
if ($key === null) {
return null;
}
$lookup = strtolower(trim($key));
return self::TABLE_MAP[$lookup] ?? null;
}
private function getPrimaryKey(string $table): string
{
return self::PRIMARY_KEYS[$table] ?? 'LogID';
}
private function getDb(): BaseConnection
{
return self::$db ??= \Config\Database::connect();
}
}

View File

@ -1,346 +1,346 @@
<?php
namespace App\Services;
use App\Libraries\ValueSet;
use CodeIgniter\Database\BaseConnection;
use CodeIgniter\HTTP\IncomingRequest;
use Config\Services;
use DateTime;
use DateTimeZone;
class AuditService
{
private const TABLE_MAP = [
'logpatient' => 'logpatient',
'patient' => 'logpatient',
'visit' => 'logpatient',
'logorder' => 'logorder',
'order' => 'logorder',
'specimen' => 'logorder',
'result' => 'logorder',
'logmaster' => 'logmaster',
'master' => 'logmaster',
'config' => 'logmaster',
'valueset' => 'logmaster',
'logsystem' => 'logsystem',
'system' => 'logsystem',
'auth' => 'logsystem',
'job' => 'logsystem',
];
private const DEFAULT_APP_ID = 'clqms-api';
private const ENTITY_VERSION_DEFAULT = 1;
private static ?BaseConnection $db = null;
private static $session = null;
private static ?IncomingRequest $request = null;
private static ?array $eventIdCache = null;
private static ?string $cachedRequestId = null;
public static function logData(
string $eventId,
string $activityId,
string $entityType,
string $entityId,
string $tableName,
?string $fldName = null,
$previousValue = null,
$newValue = null,
?string $reason = null,
?array $context = null,
array $options = []
): void {
$sourceTable = $tableName ?: $entityType;
$logTable = self::resolveLogTable($sourceTable);
if ($logTable === null) {
log_message('warning', "AuditService cannot resolve log table for {$sourceTable}");
return;
}
$record = self::buildRecord(
$logTable,
self::normalizeEventId($eventId),
strtoupper($activityId),
$entityType,
$entityId,
$sourceTable,
$fldName,
$previousValue,
$newValue,
$reason,
$context,
$options
);
if ($record === null) {
return;
}
try {
self::getDb()->table($logTable)->insert($record);
} catch (\Throwable $e) {
log_message('error', "AuditService failed to insert into {$logTable}: {$e->getMessage()}");
}
}
private static function buildRecord(
string $logTable,
string $eventId,
string $activityId,
string $entityType,
string $entityId,
string $tblName,
?string $fldName,
$previousValue,
$newValue,
?string $reason,
?array $context,
array $options
): ?array {
$contextJson = self::buildContext($context, $options, $entityType);
if ($contextJson === null) {
return null;
}
return [
'TblName' => $tblName,
'RecID' => (string) $entityId,
'FldName' => $fldName,
'FldValuePrev' => self::serializeValue($previousValue),
'FldValueNew' => self::serializeValue($newValue),
'UserID' => self::resolveUserId($options),
'SiteID' => self::resolveSiteId($options),
'DIDType' => $options['did_type'] ?? null,
'DID' => $options['did'] ?? null,
'MachineID' => $options['machine_id'] ?? gethostname(),
'SessionID' => self::resolveSessionId($options),
'AppID' => $options['app_id'] ?? self::DEFAULT_APP_ID,
'ProcessID' => $options['process_id'] ?? null,
'WebPageID' => $options['web_page_id'] ?? self::resolveRoute($options),
'EventID' => $eventId,
'ActivityID' => $activityId,
'Reason' => $reason,
'LogDate' => self::nowWithMillis(),
'Context' => $contextJson,
'IpAddress' => self::resolveIpAddress(),
];
}
private static function serializeValue($value): ?string
{
if ($value === null) {
return null;
}
if (is_scalar($value)) {
return (string) $value;
}
$json = json_encode($value, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
return $json !== false ? $json : null;
}
private static function buildContext(?array $context, array $options, string $entityType): ?string
{
$route = $options['route'] ?? self::resolveRoute($options);
$payload = array_merge(
[
'request_id' => $options['request_id'] ?? self::resolveRequestId(),
'route' => $route,
'timestamp_utc' => $options['timestamp_utc'] ?? self::timestampUtc(),
'entity_type' => $options['entity_type'] ?? $entityType,
'entity_version' => $options['entity_version'] ?? self::ENTITY_VERSION_DEFAULT,
],
$context ?? []
);
$json = json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
return $json !== false ? $json : null;
}
private static function resolveLogTable(?string $source): ?string
{
if ($source === null) {
return null;
}
$key = strtolower(trim($source));
return self::TABLE_MAP[$key] ?? null;
}
private static function resolveUserId(array $options): string
{
return $options['user_id'] ?? self::getSessionValue('user_id') ?? 'SYSTEM';
}
private static function resolveSiteId(array $options): string
{
return $options['site_id'] ?? self::getSessionValue('site_id') ?? 'GLOBAL';
}
private static function resolveSessionId(array $options): string
{
if (!empty($options['session_id'])) {
return $options['session_id'];
}
$session = self::getSession();
if ($session !== null && method_exists($session, 'getId')) {
$id = $session->getId();
if (!empty($id)) {
return $id;
}
}
if (session_status() === PHP_SESSION_ACTIVE) {
$id = session_id();
if (!empty($id)) {
return $id;
}
}
return self::generateUniqueId('sess');
}
private static function resolveRoute(array $options): string
{
if (!empty($options['route'])) {
return $options['route'];
}
$request = self::getRequest();
if ($request !== null) {
return trim(sprintf('%s %s', $request->getMethod(), $request->getUri()->getPath()));
}
return 'cli';
}
private static function resolveIpAddress(): ?string
{
$request = self::getRequest();
if ($request !== null) {
return $request->getIPAddress();
}
return $_SERVER['REMOTE_ADDR'] ?? null;
}
private static function normalizeEventId(string $eventId): string
{
$normalized = strtoupper(trim($eventId));
if (empty($normalized)) {
log_message('warning', 'AuditService received empty EventID');
return $eventId;
}
if (!self::isKnownEvent($normalized)) {
log_message('warning', "AuditService unknown EventID: {$normalized}");
}
return $normalized;
}
private static function isKnownEvent(string $eventId): bool
{
if (self::$eventIdCache === null) {
$raw = ValueSet::getRaw('event_id') ?? [];
self::$eventIdCache = array_filter(array_map(fn ($item) => $item['key'] ?? null, $raw));
}
return in_array($eventId, self::$eventIdCache, true);
}
private static function resolveRequestId(): string
{
if (self::$cachedRequestId !== null) {
return self::$cachedRequestId;
}
$request = self::getRequest();
if ($request !== null) {
$value = $request->getHeaderLine('X-Request-ID');
if (!empty($value)) {
self::$cachedRequestId = $value;
return $value;
}
}
foreach (['HTTP_X_REQUEST_ID', 'REQUEST_ID'] as $header) {
if (!empty($_SERVER[$header])) {
self::$cachedRequestId = $_SERVER[$header];
return self::$cachedRequestId;
}
}
return self::$cachedRequestId = self::generateUniqueId('req');
}
private static function nowWithMillis(): string
{
$dt = new DateTime('now', new DateTimeZone('UTC'));
return $dt->format('Y-m-d H:i:s.v');
}
private static function timestampUtc(): string
{
$dt = new DateTime('now', new DateTimeZone('UTC'));
return $dt->format('Y-m-d\TH:i:s.v\Z');
}
private static function getSessionValue(string $key): ?string
{
$session = self::getSession();
if ($session === null) {
return null;
}
if (!method_exists($session, 'get')) {
return null;
}
$value = $session->get($key);
return $value !== null ? (string) $value : null;
}
private static function getSession(): ?object
{
if (self::$session !== null) {
return self::$session;
}
try {
return self::$session = Services::session();
} catch (\Throwable $e) {
return self::$session = null;
}
}
private static function getRequest(): ?IncomingRequest
{
if (self::$request !== null) {
return self::$request;
}
try {
return self::$request = Services::request();
} catch (\Throwable $e) {
return self::$request = null;
}
}
private static function getDb(): BaseConnection
{
return self::$db ??= \Config\Database::connect();
}
private static function generateUniqueId(string $prefix): string
{
try {
return $prefix . '_' . bin2hex(random_bytes(8));
} catch (\Throwable $e) {
return uniqid("{$prefix}_", true);
}
}
}
<?php
namespace App\Services;
use App\Libraries\ValueSet;
use CodeIgniter\Database\BaseConnection;
use CodeIgniter\HTTP\IncomingRequest;
use Config\Services;
use DateTime;
use DateTimeZone;
class AuditService
{
private const TABLE_MAP = [
'logpatient' => 'logpatient',
'patient' => 'logpatient',
'visit' => 'logpatient',
'logorder' => 'logorder',
'order' => 'logorder',
'specimen' => 'logorder',
'result' => 'logorder',
'logmaster' => 'logmaster',
'master' => 'logmaster',
'config' => 'logmaster',
'valueset' => 'logmaster',
'logsystem' => 'logsystem',
'system' => 'logsystem',
'auth' => 'logsystem',
'job' => 'logsystem',
];
private const DEFAULT_APP_ID = 'clqms-api';
private const ENTITY_VERSION_DEFAULT = 1;
private static ?BaseConnection $db = null;
private static $session = null;
private static ?IncomingRequest $request = null;
private static ?array $eventIdCache = null;
private static ?string $cachedRequestId = null;
public static function logData(
string $eventId,
string $activityId,
string $entityType,
string $entityId,
string $tableName,
?string $fldName = null,
$previousValue = null,
$newValue = null,
?string $reason = null,
?array $context = null,
array $options = []
): void {
$sourceTable = $tableName ?: $entityType;
$logTable = self::resolveLogTable($sourceTable);
if ($logTable === null) {
log_message('warning', "AuditService cannot resolve log table for {$sourceTable}");
return;
}
$record = self::buildRecord(
$logTable,
self::normalizeEventId($eventId),
strtoupper($activityId),
$entityType,
$entityId,
$sourceTable,
$fldName,
$previousValue,
$newValue,
$reason,
$context,
$options
);
if ($record === null) {
return;
}
try {
self::getDb()->table($logTable)->insert($record);
} catch (\Throwable $e) {
log_message('error', "AuditService failed to insert into {$logTable}: {$e->getMessage()}");
}
}
private static function buildRecord(
string $logTable,
string $eventId,
string $activityId,
string $entityType,
string $entityId,
string $tblName,
?string $fldName,
$previousValue,
$newValue,
?string $reason,
?array $context,
array $options
): ?array {
$contextJson = self::buildContext($context, $options, $entityType);
if ($contextJson === null) {
return null;
}
return [
'TblName' => $tblName,
'RecID' => (string) $entityId,
'FldName' => $fldName,
'FldValuePrev' => self::serializeValue($previousValue),
'FldValueNew' => self::serializeValue($newValue),
'UserID' => self::resolveUserId($options),
'SiteID' => self::resolveSiteId($options),
'DIDType' => $options['did_type'] ?? null,
'DID' => $options['did'] ?? null,
'MachineID' => $options['machine_id'] ?? gethostname(),
'SessionID' => self::resolveSessionId($options),
'AppID' => $options['app_id'] ?? self::DEFAULT_APP_ID,
'ProcessID' => $options['process_id'] ?? null,
'WebPageID' => $options['web_page_id'] ?? self::resolveRoute($options),
'EventID' => $eventId,
'ActivityID' => $activityId,
'Reason' => $reason,
'LogDate' => self::nowWithMillis(),
'Context' => $contextJson,
'IpAddress' => self::resolveIpAddress(),
];
}
private static function serializeValue($value): ?string
{
if ($value === null) {
return null;
}
if (is_scalar($value)) {
return (string) $value;
}
$json = json_encode($value, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
return $json !== false ? $json : null;
}
private static function buildContext(?array $context, array $options, string $entityType): ?string
{
$route = $options['route'] ?? self::resolveRoute($options);
$payload = array_merge(
[
'request_id' => $options['request_id'] ?? self::resolveRequestId(),
'route' => $route,
'timestamp_utc' => $options['timestamp_utc'] ?? self::timestampUtc(),
'entity_type' => $options['entity_type'] ?? $entityType,
'entity_version' => $options['entity_version'] ?? self::ENTITY_VERSION_DEFAULT,
],
$context ?? []
);
$json = json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
return $json !== false ? $json : null;
}
private static function resolveLogTable(?string $source): ?string
{
if ($source === null) {
return null;
}
$key = strtolower(trim($source));
return self::TABLE_MAP[$key] ?? null;
}
private static function resolveUserId(array $options): string
{
return $options['user_id'] ?? self::getSessionValue('user_id') ?? 'SYSTEM';
}
private static function resolveSiteId(array $options): string
{
return $options['site_id'] ?? self::getSessionValue('site_id') ?? 'GLOBAL';
}
private static function resolveSessionId(array $options): string
{
if (!empty($options['session_id'])) {
return $options['session_id'];
}
$session = self::getSession();
if ($session !== null && method_exists($session, 'getId')) {
$id = $session->getId();
if (!empty($id)) {
return $id;
}
}
if (session_status() === PHP_SESSION_ACTIVE) {
$id = session_id();
if (!empty($id)) {
return $id;
}
}
return self::generateUniqueId('sess');
}
private static function resolveRoute(array $options): string
{
if (!empty($options['route'])) {
return $options['route'];
}
$request = self::getRequest();
if ($request !== null) {
return trim(sprintf('%s %s', $request->getMethod(), $request->getUri()->getPath()));
}
return 'cli';
}
private static function resolveIpAddress(): ?string
{
$request = self::getRequest();
if ($request !== null) {
return $request->getIPAddress();
}
return $_SERVER['REMOTE_ADDR'] ?? null;
}
private static function normalizeEventId(string $eventId): string
{
$normalized = strtoupper(trim($eventId));
if (empty($normalized)) {
log_message('warning', 'AuditService received empty EventID');
return $eventId;
}
if (!self::isKnownEvent($normalized)) {
log_message('warning', "AuditService unknown EventID: {$normalized}");
}
return $normalized;
}
private static function isKnownEvent(string $eventId): bool
{
if (self::$eventIdCache === null) {
$raw = ValueSet::getRaw('event_id') ?? [];
self::$eventIdCache = array_filter(array_map(fn ($item) => $item['key'] ?? null, $raw));
}
return in_array($eventId, self::$eventIdCache, true);
}
private static function resolveRequestId(): string
{
if (self::$cachedRequestId !== null) {
return self::$cachedRequestId;
}
$request = self::getRequest();
if ($request !== null) {
$value = $request->getHeaderLine('X-Request-ID');
if (!empty($value)) {
self::$cachedRequestId = $value;
return $value;
}
}
foreach (['HTTP_X_REQUEST_ID', 'REQUEST_ID'] as $header) {
if (!empty($_SERVER[$header])) {
self::$cachedRequestId = $_SERVER[$header];
return self::$cachedRequestId;
}
}
return self::$cachedRequestId = self::generateUniqueId('req');
}
private static function nowWithMillis(): string
{
$dt = new DateTime('now', new DateTimeZone('UTC'));
return $dt->format('Y-m-d H:i:s.v');
}
private static function timestampUtc(): string
{
$dt = new DateTime('now', new DateTimeZone('UTC'));
return $dt->format('Y-m-d\TH:i:s.v\Z');
}
private static function getSessionValue(string $key): ?string
{
$session = self::getSession();
if ($session === null) {
return null;
}
if (!method_exists($session, 'get')) {
return null;
}
$value = $session->get($key);
return $value !== null ? (string) $value : null;
}
private static function getSession(): ?object
{
if (self::$session !== null) {
return self::$session;
}
try {
return self::$session = Services::session();
} catch (\Throwable $e) {
return self::$session = null;
}
}
private static function getRequest(): ?IncomingRequest
{
if (self::$request !== null) {
return self::$request;
}
try {
return self::$request = Services::request();
} catch (\Throwable $e) {
return self::$request = null;
}
}
private static function getDb(): BaseConnection
{
return self::$db ??= \Config\Database::connect();
}
private static function generateUniqueId(string $prefix): string
{
try {
return $prefix . '_' . bin2hex(random_bytes(8));
} catch (\Throwable $e) {
return uniqid("{$prefix}_", true);
}
}
}

View File

@ -1,389 +1,389 @@
<?php
namespace App\Services;
use App\Models\Rule\RuleDefModel;
use App\Models\Test\TestDefSiteModel;
class RuleEngineService
{
protected RuleDefModel $ruleDefModel;
protected RuleExpressionService $expr;
public function __construct()
{
$this->ruleDefModel = new RuleDefModel();
$this->expr = new RuleExpressionService();
}
/**
* Run rules for an event.
*
* Expected context keys:
* - order: array (must include InternalOID)
* - patient: array (optional, includes Sex, Age, etc.)
* - tests: array (optional, patres rows)
*
* @param string $eventCode The event that triggered rule execution
* @param array $context Context data for rule evaluation
* @return void
*/
public function run(string $eventCode, array $context = []): void
{
$order = $context['order'] ?? null;
$testSiteID = $context['testSiteID'] ?? null;
if (is_array($order) && isset($order['TestSiteID']) && $testSiteID === null) {
$testSiteID = is_numeric($order['TestSiteID']) ? (int) $order['TestSiteID'] : null;
}
$rules = $this->ruleDefModel->getActiveByEvent($eventCode, $testSiteID);
if (empty($rules)) {
return;
}
foreach ($rules as $rule) {
$rid = (int) ($rule['RuleID'] ?? 0);
if ($rid === 0) {
continue;
}
try {
// Rules must have compiled expressions
$compiled = null;
if (!empty($rule['ConditionExprCompiled'])) {
$compiled = json_decode($rule['ConditionExprCompiled'], true);
}
if (empty($compiled) || !is_array($compiled)) {
log_message('warning', 'Rule ' . $rid . ' has no compiled expression, skipping');
continue;
}
// Evaluate condition
$conditionExpr = $compiled['conditionExpr'] ?? 'true';
$matches = $this->evaluateCondition($conditionExpr, $context);
if (!$matches) {
// Execute else actions
$actions = $compiled['else'] ?? [];
} else {
// Execute then actions
$actions = $compiled['then'] ?? [];
}
// Execute all actions
foreach ($actions as $action) {
$this->executeAction($action, $context);
}
} catch (\Throwable $e) {
log_message('error', 'Rule engine error (RuleID=' . $rid . '): ' . $e->getMessage());
continue;
}
}
}
/**
* Evaluate a condition expression
* Handles special functions like requested() by querying the database
*/
private function evaluateCondition(string $conditionExpr, array $context): bool
{
// Handle requested() function(s) by querying database
$conditionExpr = preg_replace_callback(
'/requested\s*\(\s*["\']([^"\']+)["\']\s*\)/i',
function (array $m) use ($context) {
$testCode = $m[1] ?? '';
if ($testCode === '') {
return 'false';
}
return $this->isTestRequested($testCode, $context) ? 'true' : 'false';
},
$conditionExpr
);
return $this->expr->evaluateBoolean($conditionExpr, $context);
}
/**
* Check if a test was requested for the current order
*/
private function isTestRequested(string $testCode, array $context): bool
{
$order = $context['order'] ?? null;
if (!is_array($order) || empty($order['InternalOID'])) {
return false;
}
$internalOID = (int) $order['InternalOID'];
$db = \Config\Database::connect();
// Query patres to check if test with given code exists
$result = $db->table('patres')
->select('patres.*, testdefsite.TestSiteCode')
->join('testdefsite', 'testdefsite.TestSiteID = patres.TestSiteID', 'inner')
->where('patres.OrderID', $internalOID)
->where('testdefsite.TestSiteCode', $testCode)
->where('patres.DelDate', null)
->where('testdefsite.EndDate', null)
->get()
->getRow();
return $result !== null;
}
/**
* Execute an action based on its type
*/
protected function executeAction(array $action, array $context): void
{
$type = strtoupper((string) ($action['type'] ?? ''));
switch ($type) {
case 'RESULT_SET':
case 'SET_RESULT': // legacy
$this->executeSetResult($action, $context);
break;
case 'TEST_INSERT':
case 'INSERT_TEST': // legacy
$this->executeInsertTest($action, $context);
break;
case 'TEST_DELETE':
$this->executeDeleteTest($action, $context);
break;
case 'COMMENT_INSERT':
case 'ADD_COMMENT': // legacy
$this->executeAddComment($action, $context);
break;
case 'NO_OP':
// Do nothing
break;
default:
log_message('warning', 'Unknown action type: ' . $type);
break;
}
}
/**
* Execute SET_RESULT action
*/
protected function executeSetResult(array $action, array $context): void
{
$order = $context['order'] ?? null;
if (!is_array($order) || empty($order['InternalOID'])) {
throw new \Exception('SET_RESULT requires context.order.InternalOID');
}
$internalOID = (int) $order['InternalOID'];
$testSiteID = $context['testSiteID'] ?? null;
if ($testSiteID === null && isset($order['TestSiteID'])) {
$testSiteID = is_numeric($order['TestSiteID']) ? (int) $order['TestSiteID'] : null;
}
$testCode = $action['testCode'] ?? null;
if ($testCode !== null) {
$resolvedId = $this->resolveTestSiteIdByCode($testCode);
if ($resolvedId === null) {
throw new \Exception('SET_RESULT unknown test code: ' . $testCode);
}
$testSiteID = $resolvedId;
}
if ($testSiteID === null) {
throw new \Exception('SET_RESULT requires testSiteID');
}
// Get the value
if (isset($action['valueExpr']) && is_string($action['valueExpr'])) {
$value = $this->expr->evaluate($action['valueExpr'], $context);
} else {
$value = $action['value'] ?? null;
}
$testSiteCode = $testCode ?? $this->resolveTestSiteCode($testSiteID);
$db = \Config\Database::connect();
// Check if patres row exists
$patres = $db->table('patres')
->where('OrderID', $internalOID)
->where('TestSiteID', $testSiteID)
->where('DelDate', null)
->get()
->getRowArray();
if ($patres) {
// Update existing result
$ok = $db->table('patres')
->where('OrderID', $internalOID)
->where('TestSiteID', $testSiteID)
->where('DelDate', null)
->update(['Result' => $value]);
} else {
// Insert new result row
$ok = $db->table('patres')->insert([
'OrderID' => $internalOID,
'TestSiteID' => $testSiteID,
'TestSiteCode' => $testSiteCode,
'Result' => $value,
'CreateDate' => date('Y-m-d H:i:s'),
]);
}
if ($ok === false) {
throw new \Exception('SET_RESULT update/insert failed');
}
}
/**
* Execute INSERT_TEST action - Insert a new test into patres
*/
protected function executeInsertTest(array $action, array $context): void
{
$order = $context['order'] ?? null;
if (!is_array($order) || empty($order['InternalOID'])) {
throw new \Exception('INSERT_TEST requires context.order.InternalOID');
}
$internalOID = (int) $order['InternalOID'];
$testCode = $action['testCode'] ?? null;
if (empty($testCode)) {
throw new \Exception('INSERT_TEST requires testCode');
}
// Look up TestSiteID from TestSiteCode
$testDefSiteModel = new TestDefSiteModel();
$testSite = $testDefSiteModel->where('TestSiteCode', $testCode)
->where('EndDate', null)
->first();
if (!$testSite || empty($testSite['TestSiteID'])) {
throw new \Exception('INSERT_TEST: Test not found with code: ' . $testCode);
}
$testSiteID = (int) $testSite['TestSiteID'];
$db = \Config\Database::connect();
// Check if test already exists (avoid duplicates)
$existing = $db->table('patres')
->where('OrderID', $internalOID)
->where('TestSiteID', $testSiteID)
->where('DelDate', null)
->get()
->getRow();
if ($existing) {
// Test already exists, skip
return;
}
// Insert new test row
$ok = $db->table('patres')->insert([
'OrderID' => $internalOID,
'TestSiteID' => $testSiteID,
'TestSiteCode' => $testCode,
'CreateDate' => date('Y-m-d H:i:s'),
]);
if ($ok === false) {
throw new \Exception('INSERT_TEST insert failed');
}
}
/**
* Execute ADD_COMMENT action - Add a comment to the order
*/
protected function executeAddComment(array $action, array $context): void
{
$order = $context['order'] ?? null;
if (!is_array($order) || empty($order['InternalOID'])) {
throw new \Exception('ADD_COMMENT requires context.order.InternalOID');
}
$internalOID = (int) $order['InternalOID'];
$comment = $action['comment'] ?? null;
if (empty($comment)) {
throw new \Exception('ADD_COMMENT requires comment');
}
$db = \Config\Database::connect();
// Insert comment into ordercom table
$ok = $db->table('ordercom')->insert([
'InternalOID' => $internalOID,
'Comment' => $comment,
'CreateDate' => date('Y-m-d H:i:s'),
]);
if ($ok === false) {
throw new \Exception('ADD_COMMENT insert failed');
}
}
/**
* Execute TEST_DELETE action - soft delete a test from patres
*/
protected function executeDeleteTest(array $action, array $context): void
{
$order = $context['order'] ?? null;
if (!is_array($order) || empty($order['InternalOID'])) {
throw new \Exception('TEST_DELETE requires context.order.InternalOID');
}
$internalOID = (int) $order['InternalOID'];
$testCode = $action['testCode'] ?? null;
if (empty($testCode)) {
throw new \Exception('TEST_DELETE requires testCode');
}
$testDefSiteModel = new TestDefSiteModel();
$testSite = $testDefSiteModel->where('TestSiteCode', $testCode)
->where('EndDate', null)
->first();
if (!$testSite || empty($testSite['TestSiteID'])) {
// Unknown test code: no-op
return;
}
$testSiteID = (int) $testSite['TestSiteID'];
$db = \Config\Database::connect();
// Soft delete matching patres row(s)
$db->table('patres')
->where('OrderID', $internalOID)
->where('TestSiteID', $testSiteID)
->where('DelDate', null)
->update(['DelDate' => date('Y-m-d H:i:s')]);
}
private function resolveTestSiteCode(int $testSiteID): ?string
{
try {
$testDefSiteModel = new TestDefSiteModel();
$row = $testDefSiteModel->where('TestSiteID', $testSiteID)->where('EndDate', null)->first();
return $row['TestSiteCode'] ?? null;
} catch (\Throwable $e) {
return null;
}
}
private function resolveTestSiteIdByCode(string $testSiteCode): ?int
{
try {
$testDefSiteModel = new TestDefSiteModel();
$row = $testDefSiteModel->where('TestSiteCode', $testSiteCode)->where('EndDate', null)->first();
if (empty($row['TestSiteID'])) {
return null;
}
return (int) $row['TestSiteID'];
} catch (\Throwable $e) {
return null;
}
}
}
<?php
namespace App\Services;
use App\Models\Rule\RuleDefModel;
use App\Models\Test\TestDefSiteModel;
class RuleEngineService
{
protected RuleDefModel $ruleDefModel;
protected RuleExpressionService $expr;
public function __construct()
{
$this->ruleDefModel = new RuleDefModel();
$this->expr = new RuleExpressionService();
}
/**
* Run rules for an event.
*
* Expected context keys:
* - order: array (must include InternalOID)
* - patient: array (optional, includes Sex, Age, etc.)
* - tests: array (optional, patres rows)
*
* @param string $eventCode The event that triggered rule execution
* @param array $context Context data for rule evaluation
* @return void
*/
public function run(string $eventCode, array $context = []): void
{
$order = $context['order'] ?? null;
$testSiteID = $context['testSiteID'] ?? null;
if (is_array($order) && isset($order['TestSiteID']) && $testSiteID === null) {
$testSiteID = is_numeric($order['TestSiteID']) ? (int) $order['TestSiteID'] : null;
}
$rules = $this->ruleDefModel->getActiveByEvent($eventCode, $testSiteID);
if (empty($rules)) {
return;
}
foreach ($rules as $rule) {
$rid = (int) ($rule['RuleID'] ?? 0);
if ($rid === 0) {
continue;
}
try {
// Rules must have compiled expressions
$compiled = null;
if (!empty($rule['ConditionExprCompiled'])) {
$compiled = json_decode($rule['ConditionExprCompiled'], true);
}
if (empty($compiled) || !is_array($compiled)) {
log_message('warning', 'Rule ' . $rid . ' has no compiled expression, skipping');
continue;
}
// Evaluate condition
$conditionExpr = $compiled['conditionExpr'] ?? 'true';
$matches = $this->evaluateCondition($conditionExpr, $context);
if (!$matches) {
// Execute else actions
$actions = $compiled['else'] ?? [];
} else {
// Execute then actions
$actions = $compiled['then'] ?? [];
}
// Execute all actions
foreach ($actions as $action) {
$this->executeAction($action, $context);
}
} catch (\Throwable $e) {
log_message('error', 'Rule engine error (RuleID=' . $rid . '): ' . $e->getMessage());
continue;
}
}
}
/**
* Evaluate a condition expression
* Handles special functions like requested() by querying the database
*/
private function evaluateCondition(string $conditionExpr, array $context): bool
{
// Handle requested() function(s) by querying database
$conditionExpr = preg_replace_callback(
'/requested\s*\(\s*["\']([^"\']+)["\']\s*\)/i',
function (array $m) use ($context) {
$testCode = $m[1] ?? '';
if ($testCode === '') {
return 'false';
}
return $this->isTestRequested($testCode, $context) ? 'true' : 'false';
},
$conditionExpr
);
return $this->expr->evaluateBoolean($conditionExpr, $context);
}
/**
* Check if a test was requested for the current order
*/
private function isTestRequested(string $testCode, array $context): bool
{
$order = $context['order'] ?? null;
if (!is_array($order) || empty($order['InternalOID'])) {
return false;
}
$internalOID = (int) $order['InternalOID'];
$db = \Config\Database::connect();
// Query patres to check if test with given code exists
$result = $db->table('patres')
->select('patres.*, testdefsite.TestSiteCode')
->join('testdefsite', 'testdefsite.TestSiteID = patres.TestSiteID', 'inner')
->where('patres.OrderID', $internalOID)
->where('testdefsite.TestSiteCode', $testCode)
->where('patres.DelDate', null)
->where('testdefsite.EndDate', null)
->get()
->getRow();
return $result !== null;
}
/**
* Execute an action based on its type
*/
protected function executeAction(array $action, array $context): void
{
$type = strtoupper((string) ($action['type'] ?? ''));
switch ($type) {
case 'RESULT_SET':
case 'SET_RESULT': // legacy
$this->executeSetResult($action, $context);
break;
case 'TEST_INSERT':
case 'INSERT_TEST': // legacy
$this->executeInsertTest($action, $context);
break;
case 'TEST_DELETE':
$this->executeDeleteTest($action, $context);
break;
case 'COMMENT_INSERT':
case 'ADD_COMMENT': // legacy
$this->executeAddComment($action, $context);
break;
case 'NO_OP':
// Do nothing
break;
default:
log_message('warning', 'Unknown action type: ' . $type);
break;
}
}
/**
* Execute SET_RESULT action
*/
protected function executeSetResult(array $action, array $context): void
{
$order = $context['order'] ?? null;
if (!is_array($order) || empty($order['InternalOID'])) {
throw new \Exception('SET_RESULT requires context.order.InternalOID');
}
$internalOID = (int) $order['InternalOID'];
$testSiteID = $context['testSiteID'] ?? null;
if ($testSiteID === null && isset($order['TestSiteID'])) {
$testSiteID = is_numeric($order['TestSiteID']) ? (int) $order['TestSiteID'] : null;
}
$testCode = $action['testCode'] ?? null;
if ($testCode !== null) {
$resolvedId = $this->resolveTestSiteIdByCode($testCode);
if ($resolvedId === null) {
throw new \Exception('SET_RESULT unknown test code: ' . $testCode);
}
$testSiteID = $resolvedId;
}
if ($testSiteID === null) {
throw new \Exception('SET_RESULT requires testSiteID');
}
// Get the value
if (isset($action['valueExpr']) && is_string($action['valueExpr'])) {
$value = $this->expr->evaluate($action['valueExpr'], $context);
} else {
$value = $action['value'] ?? null;
}
$testSiteCode = $testCode ?? $this->resolveTestSiteCode($testSiteID);
$db = \Config\Database::connect();
// Check if patres row exists
$patres = $db->table('patres')
->where('OrderID', $internalOID)
->where('TestSiteID', $testSiteID)
->where('DelDate', null)
->get()
->getRowArray();
if ($patres) {
// Update existing result
$ok = $db->table('patres')
->where('OrderID', $internalOID)
->where('TestSiteID', $testSiteID)
->where('DelDate', null)
->update(['Result' => $value]);
} else {
// Insert new result row
$ok = $db->table('patres')->insert([
'OrderID' => $internalOID,
'TestSiteID' => $testSiteID,
'TestSiteCode' => $testSiteCode,
'Result' => $value,
'CreateDate' => date('Y-m-d H:i:s'),
]);
}
if ($ok === false) {
throw new \Exception('SET_RESULT update/insert failed');
}
}
/**
* Execute INSERT_TEST action - Insert a new test into patres
*/
protected function executeInsertTest(array $action, array $context): void
{
$order = $context['order'] ?? null;
if (!is_array($order) || empty($order['InternalOID'])) {
throw new \Exception('INSERT_TEST requires context.order.InternalOID');
}
$internalOID = (int) $order['InternalOID'];
$testCode = $action['testCode'] ?? null;
if (empty($testCode)) {
throw new \Exception('INSERT_TEST requires testCode');
}
// Look up TestSiteID from TestSiteCode
$testDefSiteModel = new TestDefSiteModel();
$testSite = $testDefSiteModel->where('TestSiteCode', $testCode)
->where('EndDate', null)
->first();
if (!$testSite || empty($testSite['TestSiteID'])) {
throw new \Exception('INSERT_TEST: Test not found with code: ' . $testCode);
}
$testSiteID = (int) $testSite['TestSiteID'];
$db = \Config\Database::connect();
// Check if test already exists (avoid duplicates)
$existing = $db->table('patres')
->where('OrderID', $internalOID)
->where('TestSiteID', $testSiteID)
->where('DelDate', null)
->get()
->getRow();
if ($existing) {
// Test already exists, skip
return;
}
// Insert new test row
$ok = $db->table('patres')->insert([
'OrderID' => $internalOID,
'TestSiteID' => $testSiteID,
'TestSiteCode' => $testCode,
'CreateDate' => date('Y-m-d H:i:s'),
]);
if ($ok === false) {
throw new \Exception('INSERT_TEST insert failed');
}
}
/**
* Execute ADD_COMMENT action - Add a comment to the order
*/
protected function executeAddComment(array $action, array $context): void
{
$order = $context['order'] ?? null;
if (!is_array($order) || empty($order['InternalOID'])) {
throw new \Exception('ADD_COMMENT requires context.order.InternalOID');
}
$internalOID = (int) $order['InternalOID'];
$comment = $action['comment'] ?? null;
if (empty($comment)) {
throw new \Exception('ADD_COMMENT requires comment');
}
$db = \Config\Database::connect();
// Insert comment into ordercom table
$ok = $db->table('ordercom')->insert([
'InternalOID' => $internalOID,
'Comment' => $comment,
'CreateDate' => date('Y-m-d H:i:s'),
]);
if ($ok === false) {
throw new \Exception('ADD_COMMENT insert failed');
}
}
/**
* Execute TEST_DELETE action - soft delete a test from patres
*/
protected function executeDeleteTest(array $action, array $context): void
{
$order = $context['order'] ?? null;
if (!is_array($order) || empty($order['InternalOID'])) {
throw new \Exception('TEST_DELETE requires context.order.InternalOID');
}
$internalOID = (int) $order['InternalOID'];
$testCode = $action['testCode'] ?? null;
if (empty($testCode)) {
throw new \Exception('TEST_DELETE requires testCode');
}
$testDefSiteModel = new TestDefSiteModel();
$testSite = $testDefSiteModel->where('TestSiteCode', $testCode)
->where('EndDate', null)
->first();
if (!$testSite || empty($testSite['TestSiteID'])) {
// Unknown test code: no-op
return;
}
$testSiteID = (int) $testSite['TestSiteID'];
$db = \Config\Database::connect();
// Soft delete matching patres row(s)
$db->table('patres')
->where('OrderID', $internalOID)
->where('TestSiteID', $testSiteID)
->where('DelDate', null)
->update(['DelDate' => date('Y-m-d H:i:s')]);
}
private function resolveTestSiteCode(int $testSiteID): ?string
{
try {
$testDefSiteModel = new TestDefSiteModel();
$row = $testDefSiteModel->where('TestSiteID', $testSiteID)->where('EndDate', null)->first();
return $row['TestSiteCode'] ?? null;
} catch (\Throwable $e) {
return null;
}
}
private function resolveTestSiteIdByCode(string $testSiteCode): ?int
{
try {
$testDefSiteModel = new TestDefSiteModel();
$row = $testDefSiteModel->where('TestSiteCode', $testSiteCode)->where('EndDate', null)->first();
if (empty($row['TestSiteID'])) {
return null;
}
return (int) $row['TestSiteID'];
} catch (\Throwable $e) {
return null;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,20 +1,20 @@
<?php
namespace App\Traits;
trait PatchValidationTrait {
protected function requirePatchId(mixed $id, string $label = 'ID'): ?int {
if ($id === null || $id === '' || !ctype_digit((string) $id)) {
$this->failValidationErrors("{$label} is required and must be a valid integer.");
return null;
}
return (int) $id;
}
protected function requirePatchPayload(mixed $payload): ?array {
if (!is_array($payload) || empty($payload)) {
$this->failValidationErrors('No data provided for update.');
return null;
}
return $payload;
}
}
<?php
namespace App\Traits;
trait PatchValidationTrait {
protected function requirePatchId(mixed $id, string $label = 'ID'): ?int {
if ($id === null || $id === '' || !ctype_digit((string) $id)) {
$this->failValidationErrors("{$label} is required and must be a valid integer.");
return null;
}
return (int) $id;
}
protected function requirePatchPayload(mixed $payload): ?array {
if (!is_array($payload) || empty($payload)) {
$this->failValidationErrors('No data provided for update.');
return null;
}
return $payload;
}
}

69
composer.lock generated
View File

@ -88,12 +88,12 @@
"version": "v7.0.5",
"source": {
"type": "git",
"url": "https://github.com/firebase/php-jwt.git",
"url": "https://github.com/googleapis/php-jwt.git",
"reference": "47ad26bab5e7c70ae8a6f08ed25ff83631121380"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/firebase/php-jwt/zipball/47ad26bab5e7c70ae8a6f08ed25ff83631121380",
"url": "https://api.github.com/repos/googleapis/php-jwt/zipball/47ad26bab5e7c70ae8a6f08ed25ff83631121380",
"reference": "47ad26bab5e7c70ae8a6f08ed25ff83631121380",
"shasum": ""
},
@ -142,8 +142,8 @@
"php"
],
"support": {
"issues": "https://github.com/firebase/php-jwt/issues",
"source": "https://github.com/firebase/php-jwt/tree/v7.0.5"
"issues": "https://github.com/googleapis/php-jwt/issues",
"source": "https://github.com/googleapis/php-jwt/tree/v7.0.5"
},
"time": "2026-04-01T20:38:03+00:00"
},
@ -362,30 +362,34 @@
},
{
"name": "symfony/cache",
"version": "v8.0.8",
"version": "v7.4.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/cache.git",
"reference": "8abf3ccbeae9d3071b81a3ae7ee11b209f9e1e78"
"reference": "467464da294734b0fb17e853e5712abc8470f819"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/cache/zipball/8abf3ccbeae9d3071b81a3ae7ee11b209f9e1e78",
"reference": "8abf3ccbeae9d3071b81a3ae7ee11b209f9e1e78",
"url": "https://api.github.com/repos/symfony/cache/zipball/467464da294734b0fb17e853e5712abc8470f819",
"reference": "467464da294734b0fb17e853e5712abc8470f819",
"shasum": ""
},
"require": {
"php": ">=8.4",
"php": ">=8.2",
"psr/cache": "^2.0|^3.0",
"psr/log": "^1.1|^2|^3",
"symfony/cache-contracts": "^3.6",
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/service-contracts": "^2.5|^3",
"symfony/var-exporter": "^7.4|^8.0"
"symfony/var-exporter": "^6.4|^7.0|^8.0"
},
"conflict": {
"doctrine/dbal": "<4.3",
"doctrine/dbal": "<3.6",
"ext-redis": "<6.1",
"ext-relay": "<0.12.1"
"ext-relay": "<0.12.1",
"symfony/dependency-injection": "<6.4",
"symfony/http-kernel": "<6.4",
"symfony/var-dumper": "<6.4"
},
"provide": {
"psr/cache-implementation": "2.0|3.0",
@ -394,16 +398,16 @@
},
"require-dev": {
"cache/integration-tests": "dev-master",
"doctrine/dbal": "^4.3",
"doctrine/dbal": "^3.6|^4",
"predis/predis": "^1.1|^2.0",
"psr/simple-cache": "^1.0|^2.0|^3.0",
"symfony/clock": "^7.4|^8.0",
"symfony/config": "^7.4|^8.0",
"symfony/dependency-injection": "^7.4|^8.0",
"symfony/filesystem": "^7.4|^8.0",
"symfony/http-kernel": "^7.4|^8.0",
"symfony/messenger": "^7.4|^8.0",
"symfony/var-dumper": "^7.4|^8.0"
"symfony/clock": "^6.4|^7.0|^8.0",
"symfony/config": "^6.4|^7.0|^8.0",
"symfony/dependency-injection": "^6.4|^7.0|^8.0",
"symfony/filesystem": "^6.4|^7.0|^8.0",
"symfony/http-kernel": "^6.4|^7.0|^8.0",
"symfony/messenger": "^6.4|^7.0|^8.0",
"symfony/var-dumper": "^6.4|^7.0|^8.0"
},
"type": "library",
"autoload": {
@ -438,7 +442,7 @@
"psr6"
],
"support": {
"source": "https://github.com/symfony/cache/tree/v8.0.8"
"source": "https://github.com/symfony/cache/tree/v7.4.8"
},
"funding": [
{
@ -458,7 +462,7 @@
"type": "tidelift"
}
],
"time": "2026-03-30T15:18:51+00:00"
"time": "2026-03-30T15:15:47+00:00"
},
{
"name": "symfony/cache-contracts",
@ -760,25 +764,26 @@
},
{
"name": "symfony/var-exporter",
"version": "v8.0.8",
"version": "v7.4.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-exporter.git",
"reference": "15776bb07a91b089037da89f8832fa41d5fa6ec6"
"reference": "398907e89a2a56fe426f7955c6fa943ec0c77225"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/var-exporter/zipball/15776bb07a91b089037da89f8832fa41d5fa6ec6",
"reference": "15776bb07a91b089037da89f8832fa41d5fa6ec6",
"url": "https://api.github.com/repos/symfony/var-exporter/zipball/398907e89a2a56fe426f7955c6fa943ec0c77225",
"reference": "398907e89a2a56fe426f7955c6fa943ec0c77225",
"shasum": ""
},
"require": {
"php": ">=8.4"
"php": ">=8.2",
"symfony/deprecation-contracts": "^2.5|^3"
},
"require-dev": {
"symfony/property-access": "^7.4|^8.0",
"symfony/serializer": "^7.4|^8.0",
"symfony/var-dumper": "^7.4|^8.0"
"symfony/property-access": "^6.4|^7.0|^8.0",
"symfony/serializer": "^6.4|^7.0|^8.0",
"symfony/var-dumper": "^6.4|^7.0|^8.0"
},
"type": "library",
"autoload": {
@ -816,7 +821,7 @@
"serialize"
],
"support": {
"source": "https://github.com/symfony/var-exporter/tree/v8.0.8"
"source": "https://github.com/symfony/var-exporter/tree/v7.4.8"
},
"funding": [
{
@ -836,7 +841,7 @@
"type": "tidelift"
}
],
"time": "2026-03-30T15:14:47+00:00"
"time": "2026-03-24T13:12:05+00:00"
}
],
"packages-dev": [

12
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "clqms01-be",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}
{
"name": "clqms01-be",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,107 +1,107 @@
AuditLogEntry:
type: object
properties:
LogPatientID:
type: integer
nullable: true
LogOrderID:
type: integer
nullable: true
LogMasterID:
type: integer
nullable: true
LogSystemID:
type: integer
nullable: true
TblName:
type: string
RecID:
type: string
FldName:
type: string
nullable: true
FldValuePrev:
type: string
nullable: true
FldValueNew:
type: string
nullable: true
UserID:
type: string
SiteID:
type: string
DIDType:
type: string
nullable: true
DID:
type: string
nullable: true
MachineID:
type: string
nullable: true
SessionID:
type: string
AppID:
type: string
ProcessID:
type: string
nullable: true
WebPageID:
type: string
nullable: true
EventID:
type: string
ActivityID:
type: string
Reason:
type: string
nullable: true
LogDate:
type: string
format: date-time
Context:
type: string
IpAddress:
type: string
nullable: true
AuditLogListResponse:
type: object
properties:
data:
type: array
items:
$ref: '#/AuditLogEntry'
pagination:
type: object
properties:
page:
type: integer
perPage:
type: integer
total:
type: integer
required: [page, perPage, total]
required: [data, pagination]
AuditLogsEnvelope:
type: object
properties:
status:
type: string
message:
type: string
data:
$ref: '#/AuditLogListResponse'
required: [status, message, data]
AuditLogsErrorResponse:
type: object
properties:
status:
type: string
message:
type: string
data:
nullable: true
required: [status, message, data]
AuditLogEntry:
type: object
properties:
LogPatientID:
type: integer
nullable: true
LogOrderID:
type: integer
nullable: true
LogMasterID:
type: integer
nullable: true
LogSystemID:
type: integer
nullable: true
TblName:
type: string
RecID:
type: string
FldName:
type: string
nullable: true
FldValuePrev:
type: string
nullable: true
FldValueNew:
type: string
nullable: true
UserID:
type: string
SiteID:
type: string
DIDType:
type: string
nullable: true
DID:
type: string
nullable: true
MachineID:
type: string
nullable: true
SessionID:
type: string
AppID:
type: string
ProcessID:
type: string
nullable: true
WebPageID:
type: string
nullable: true
EventID:
type: string
ActivityID:
type: string
Reason:
type: string
nullable: true
LogDate:
type: string
format: date-time
Context:
type: string
IpAddress:
type: string
nullable: true
AuditLogListResponse:
type: object
properties:
data:
type: array
items:
$ref: '#/AuditLogEntry'
pagination:
type: object
properties:
page:
type: integer
perPage:
type: integer
total:
type: integer
required: [page, perPage, total]
required: [data, pagination]
AuditLogsEnvelope:
type: object
properties:
status:
type: string
message:
type: string
data:
$ref: '#/AuditLogListResponse'
required: [status, message, data]
AuditLogsErrorResponse:
type: object
properties:
status:
type: string
message:
type: string
data:
nullable: true
required: [status, message, data]

View File

@ -1,121 +1,121 @@
User:
type: object
properties:
UserID:
type: integer
description: Unique user identifier
Username:
type: string
description: Unique login username
Email:
type: string
format: email
description: User email address
Name:
type: string
description: Full name of the user
Role:
type: string
description: User role (admin, technician, doctor, etc.)
Department:
type: string
description: Department name
IsActive:
type: boolean
description: Whether the user account is active
CreatedAt:
type: string
format: date-time
description: Creation timestamp
UpdatedAt:
type: string
format: date-time
description: Last update timestamp
DelDate:
type: string
format: date-time
nullable: true
description: Soft delete timestamp (null if active)
UserCreate:
type: object
required:
- Username
- Email
properties:
Username:
type: string
minLength: 3
maxLength: 50
description: Unique login username
Email:
type: string
format: email
maxLength: 100
description: User email address
Name:
type: string
description: Full name of the user
Role:
type: string
description: User role
Department:
type: string
description: Department name
IsActive:
type: boolean
default: true
description: Whether the user account is active
UserUpdate:
type: object
required:
- UserID
properties:
UserID:
type: integer
description: User ID to update
Email:
type: string
format: email
description: User email address
Name:
type: string
description: Full name of the user
Role:
type: string
description: User role
Department:
type: string
description: Department name
IsActive:
type: boolean
description: Whether the user account is active
UserListResponse:
type: object
properties:
status:
type: string
example: success
message:
type: string
example: Users retrieved successfully
data:
type: object
properties:
users:
type: array
items:
$ref: '#/User'
pagination:
type: object
properties:
current_page:
type: integer
per_page:
type: integer
total:
type: integer
total_pages:
User:
type: object
properties:
UserID:
type: integer
description: Unique user identifier
Username:
type: string
description: Unique login username
Email:
type: string
format: email
description: User email address
Name:
type: string
description: Full name of the user
Role:
type: string
description: User role (admin, technician, doctor, etc.)
Department:
type: string
description: Department name
IsActive:
type: boolean
description: Whether the user account is active
CreatedAt:
type: string
format: date-time
description: Creation timestamp
UpdatedAt:
type: string
format: date-time
description: Last update timestamp
DelDate:
type: string
format: date-time
nullable: true
description: Soft delete timestamp (null if active)
UserCreate:
type: object
required:
- Username
- Email
properties:
Username:
type: string
minLength: 3
maxLength: 50
description: Unique login username
Email:
type: string
format: email
maxLength: 100
description: User email address
Name:
type: string
description: Full name of the user
Role:
type: string
description: User role
Department:
type: string
description: Department name
IsActive:
type: boolean
default: true
description: Whether the user account is active
UserUpdate:
type: object
required:
- UserID
properties:
UserID:
type: integer
description: User ID to update
Email:
type: string
format: email
description: User email address
Name:
type: string
description: Full name of the user
Role:
type: string
description: User role
Department:
type: string
description: Department name
IsActive:
type: boolean
description: Whether the user account is active
UserListResponse:
type: object
properties:
status:
type: string
example: success
message:
type: string
example: Users retrieved successfully
data:
type: object
properties:
users:
type: array
items:
$ref: '#/User'
pagination:
type: object
properties:
current_page:
type: integer
per_page:
type: integer
total:
type: integer
total_pages:
type: integer

View File

@ -1,76 +1,76 @@
/api/audit-logs:
get:
tags: [Audit]
summary: Retrieve audit log entries for a table
security:
- bearerAuth: []
parameters:
- name: table
in: query
required: true
schema:
type: string
description: Table alias for the audit data (logpatient, logorder, logmaster, logsystem)
- name: rec_id
in: query
schema:
type: string
description: Primary record identifier (RecID) to filter audit rows
- name: event_id
in: query
schema:
type: string
description: Canonical EventID (case insensitive)
- name: activity_id
in: query
schema:
type: string
description: Canonical ActivityID (case insensitive)
- name: from
in: query
schema:
type: string
format: date-time
description: Lower bound for LogDate inclusive
- name: to
in: query
schema:
type: string
format: date-time
description: Upper bound for LogDate inclusive
- name: search
in: query
schema:
type: string
description: Search term that matches user, reason, field names, or values
- name: page
in: query
schema:
type: integer
default: 1
description: Page number
- name: perPage
in: query
schema:
type: integer
default: 20
description: Items per page (max 100)
responses:
'200':
description: Audit log results
content:
application/json:
schema:
$ref: '../components/schemas/audit-logs.yaml#/AuditLogsEnvelope'
'400':
description: Validation failure (missing table or invalid filters)
content:
application/json:
schema:
$ref: '../components/schemas/audit-logs.yaml#/AuditLogsErrorResponse'
'500':
description: Internal error when retrieving audit logs
content:
application/json:
schema:
$ref: '../components/schemas/audit-logs.yaml#/AuditLogsErrorResponse'
/api/audit-logs:
get:
tags: [Audit]
summary: Retrieve audit log entries for a table
security:
- bearerAuth: []
parameters:
- name: table
in: query
required: true
schema:
type: string
description: Table alias for the audit data (logpatient, logorder, logmaster, logsystem)
- name: rec_id
in: query
schema:
type: string
description: Primary record identifier (RecID) to filter audit rows
- name: event_id
in: query
schema:
type: string
description: Canonical EventID (case insensitive)
- name: activity_id
in: query
schema:
type: string
description: Canonical ActivityID (case insensitive)
- name: from
in: query
schema:
type: string
format: date-time
description: Lower bound for LogDate inclusive
- name: to
in: query
schema:
type: string
format: date-time
description: Upper bound for LogDate inclusive
- name: search
in: query
schema:
type: string
description: Search term that matches user, reason, field names, or values
- name: page
in: query
schema:
type: integer
default: 1
description: Page number
- name: perPage
in: query
schema:
type: integer
default: 20
description: Items per page (max 100)
responses:
'200':
description: Audit log results
content:
application/json:
schema:
$ref: '../components/schemas/audit-logs.yaml#/AuditLogsEnvelope'
'400':
description: Validation failure (missing table or invalid filters)
content:
application/json:
schema:
$ref: '../components/schemas/audit-logs.yaml#/AuditLogsErrorResponse'
'500':
description: Internal error when retrieving audit logs
content:
application/json:
schema:
$ref: '../components/schemas/audit-logs.yaml#/AuditLogsErrorResponse'

View File

@ -1,112 +1,112 @@
/api/calc/testcode/{codeOrName}:
post:
tags: [Calculation]
summary: Evaluate a configured calculation by test code or name and return the raw result map.
security: []
parameters:
- name: codeOrName
in: path
required: true
schema:
type: string
description: TestSiteCode or TestSiteName of the calculated test (case-insensitive).
requestBody:
required: true
content:
application/json:
schema:
type: object
description: Key-value pairs where keys match member tests used in the formula.
additionalProperties:
type: number
example:
TBIL: 5
DBIL: 3
responses:
'200':
description: Returns a single key/value pair with the canonical TestSiteCode or an empty object when the calculation is incomplete or missing.
content:
application/json:
schema:
type: object
examples:
success:
value:
IBIL: 2.0
incomplete:
value: {}
/api/calc/testsite/{testSiteID}:
post:
tags: [Calculation]
summary: Evaluate a calculation defined for a test site and return a structured result.
security: []
parameters:
- name: testSiteID
in: path
required: true
schema:
type: integer
description: Identifier for the test site whose definition should be evaluated.
requestBody:
required: true
content:
application/json:
schema:
type: object
description: Variable assignments required by the test site formula.
additionalProperties:
type: number
example:
result: 85
gender: "female"
age: 30
responses:
'200':
description: Returns the calculated result, testSiteID, formula code, and echoed variables.
content:
application/json:
schema:
type: object
properties:
status:
type: string
example: success
data:
type: object
properties:
result:
type: number
testSiteID:
type: integer
formula:
type: string
variables:
type: object
additionalProperties:
type: number
examples:
success:
value:
status: success
data:
result: 92.4
testSiteID: 123
formula: "{result} * {factor} + {age}"
variables:
result: 85
gender: female
age: 30
'404':
description: No calculation defined for the requested test site.
content:
application/json:
schema:
type: object
properties:
status:
type: string
example: failed
message:
type: string
example: No calculation defined for this test site
/api/calc/testcode/{codeOrName}:
post:
tags: [Calculation]
summary: Evaluate a configured calculation by test code or name and return the raw result map.
security: []
parameters:
- name: codeOrName
in: path
required: true
schema:
type: string
description: TestSiteCode or TestSiteName of the calculated test (case-insensitive).
requestBody:
required: true
content:
application/json:
schema:
type: object
description: Key-value pairs where keys match member tests used in the formula.
additionalProperties:
type: number
example:
TBIL: 5
DBIL: 3
responses:
'200':
description: Returns a single key/value pair with the canonical TestSiteCode or an empty object when the calculation is incomplete or missing.
content:
application/json:
schema:
type: object
examples:
success:
value:
IBIL: 2.0
incomplete:
value: {}
/api/calc/testsite/{testSiteID}:
post:
tags: [Calculation]
summary: Evaluate a calculation defined for a test site and return a structured result.
security: []
parameters:
- name: testSiteID
in: path
required: true
schema:
type: integer
description: Identifier for the test site whose definition should be evaluated.
requestBody:
required: true
content:
application/json:
schema:
type: object
description: Variable assignments required by the test site formula.
additionalProperties:
type: number
example:
result: 85
gender: "female"
age: 30
responses:
'200':
description: Returns the calculated result, testSiteID, formula code, and echoed variables.
content:
application/json:
schema:
type: object
properties:
status:
type: string
example: success
data:
type: object
properties:
result:
type: number
testSiteID:
type: integer
formula:
type: string
variables:
type: object
additionalProperties:
type: number
examples:
success:
value:
status: success
data:
result: 92.4
testSiteID: 123
formula: "{result} * {factor} + {age}"
variables:
result: 85
gender: female
age: 30
'404':
description: No calculation defined for the requested test site.
content:
application/json:
schema:
type: object
properties:
status:
type: string
example: failed
message:
type: string
example: No calculation defined for this test site

View File

@ -1,220 +1,220 @@
/api/user:
get:
tags: [User]
summary: List users with pagination and search
security:
- bearerAuth: []
parameters:
- name: page
in: query
schema:
type: integer
default: 1
description: Page number
- name: per_page
in: query
schema:
type: integer
default: 20
description: Items per page
- name: search
in: query
schema:
type: string
description: Search term for username, email, or name
responses:
'200':
description: List of users with pagination
content:
application/json:
schema:
type: object
properties:
status:
type: string
example: success
message:
type: string
example: Users retrieved successfully
data:
type: object
properties:
users:
type: array
items:
$ref: '../components/schemas/user.yaml#/User'
pagination:
type: object
properties:
current_page:
type: integer
per_page:
type: integer
total:
type: integer
total_pages:
type: integer
'500':
description: Server error
post:
tags: [User]
summary: Create new user
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '../components/schemas/user.yaml#/UserCreate'
responses:
'201':
description: User created successfully
content:
application/json:
schema:
type: object
properties:
status:
type: string
example: success
message:
type: string
example: User created successfully
data:
type: object
properties:
UserID:
type: integer
Username:
type: string
Email:
type: string
'400':
description: Validation failed
content:
application/json:
schema:
type: object
properties:
status:
type: string
example: failed
message:
type: string
example: Validation failed
data:
type: object
'500':
description: Server error
/api/user/{id}:
get:
tags: [User]
summary: Get user by ID
security:
- bearerAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: integer
description: User ID
responses:
'200':
description: User details
content:
application/json:
schema:
$ref: '../components/schemas/user.yaml#/User'
'404':
description: User not found
'500':
description: Server error
patch:
tags: [User]
summary: Update existing user
security:
- bearerAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: integer
description: User ID
requestBody:
required: true
content:
application/json:
schema:
$ref: '../components/schemas/user.yaml#/UserUpdate'
responses:
'200':
description: User updated successfully
content:
application/json:
schema:
type: object
properties:
status:
type: string
example: success
message:
type: string
example: User updated successfully
data:
type: object
properties:
UserID:
type: integer
updated_fields:
type: array
items:
type: string
'400':
description: UserID is required
'404':
description: User not found
'500':
description: Server error
delete:
tags: [User]
summary: Delete user (soft delete)
security:
- bearerAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: integer
description: User ID
responses:
'200':
description: User deleted successfully
content:
application/json:
schema:
type: object
properties:
status:
type: string
example: success
message:
type: string
example: User deleted successfully
data:
type: object
properties:
UserID:
type: integer
'404':
description: User not found
'500':
description: Server error
/api/user:
get:
tags: [User]
summary: List users with pagination and search
security:
- bearerAuth: []
parameters:
- name: page
in: query
schema:
type: integer
default: 1
description: Page number
- name: per_page
in: query
schema:
type: integer
default: 20
description: Items per page
- name: search
in: query
schema:
type: string
description: Search term for username, email, or name
responses:
'200':
description: List of users with pagination
content:
application/json:
schema:
type: object
properties:
status:
type: string
example: success
message:
type: string
example: Users retrieved successfully
data:
type: object
properties:
users:
type: array
items:
$ref: '../components/schemas/user.yaml#/User'
pagination:
type: object
properties:
current_page:
type: integer
per_page:
type: integer
total:
type: integer
total_pages:
type: integer
'500':
description: Server error
post:
tags: [User]
summary: Create new user
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '../components/schemas/user.yaml#/UserCreate'
responses:
'201':
description: User created successfully
content:
application/json:
schema:
type: object
properties:
status:
type: string
example: success
message:
type: string
example: User created successfully
data:
type: object
properties:
UserID:
type: integer
Username:
type: string
Email:
type: string
'400':
description: Validation failed
content:
application/json:
schema:
type: object
properties:
status:
type: string
example: failed
message:
type: string
example: Validation failed
data:
type: object
'500':
description: Server error
/api/user/{id}:
get:
tags: [User]
summary: Get user by ID
security:
- bearerAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: integer
description: User ID
responses:
'200':
description: User details
content:
application/json:
schema:
$ref: '../components/schemas/user.yaml#/User'
'404':
description: User not found
'500':
description: Server error
patch:
tags: [User]
summary: Update existing user
security:
- bearerAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: integer
description: User ID
requestBody:
required: true
content:
application/json:
schema:
$ref: '../components/schemas/user.yaml#/UserUpdate'
responses:
'200':
description: User updated successfully
content:
application/json:
schema:
type: object
properties:
status:
type: string
example: success
message:
type: string
example: User updated successfully
data:
type: object
properties:
UserID:
type: integer
updated_fields:
type: array
items:
type: string
'400':
description: UserID is required
'404':
description: User not found
'500':
description: Server error
delete:
tags: [User]
summary: Delete user (soft delete)
security:
- bearerAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: integer
description: User ID
responses:
'200':
description: User deleted successfully
content:
application/json:
schema:
type: object
properties:
status:
type: string
example: success
message:
type: string
example: User deleted successfully
data:
type: object
properties:
UserID:
type: integer
'404':
description: User not found
'500':
description: Server error

View File

@ -1,62 +1,62 @@
<?php
namespace Tests\Support\Traits;
use App\Models\Patient\PatientModel;
use Faker\Factory;
trait CreatesPatients
{
protected function createTestPatient(array $overrides = []): int
{
$faker = Factory::create('id_ID');
$patientPayload = array_merge([
'PatientID' => 'PAT' . $faker->numerify('##########'),
'AlternatePID' => 'ALT' . $faker->numerify('##########'),
'Prefix' => $faker->title,
'NameFirst' => 'Test',
'NameMiddle' => $faker->firstName,
'NameLast' => 'Patient',
'Suffix' => 'S.Kom',
'Sex' => (string) $faker->numberBetween(5, 6),
'PlaceOfBirth' => $faker->city,
'Birthdate' => $faker->date('Y-m-d'),
'ZIP' => $faker->postcode,
'Street_1' => $faker->streetAddress,
'City' => $faker->city,
'Province' => $faker->state,
'EmailAddress1' => 'test.' . $faker->unique()->userName . '@example.com',
'Phone' => $faker->numerify('08##########'),
'MobilePhone' => $faker->numerify('08##########'),
'Race' => (string) $faker->numberBetween(175, 205),
'Country' => (string) $faker->numberBetween(221, 469),
'MaritalStatus' => (string) $faker->numberBetween(8, 15),
'Religion' => (string) $faker->numberBetween(206, 212),
'Ethnic' => (string) $faker->numberBetween(213, 220),
'Citizenship' => 'WNI',
'isDead' => (string) $faker->numberBetween(0, 1),
'PatIdt' => [
'IdentifierType' => 'ID',
'Identifier' => $faker->numerify('################')
],
'PatAtt' => [
[ 'Address' => '/api/upload/' . $faker->uuid . '.jpg' ]
],
'PatCom' => $faker->sentence,
], $overrides);
if ($patientPayload['isDead'] === '1') {
$patientPayload['DeathDateTime'] = $faker->date('Y-m-d H:i:s');
} else {
$patientPayload['DeathDateTime'] = null;
}
$patientModel = new PatientModel();
$internalPID = $patientModel->createPatient($patientPayload);
if (!$internalPID) {
throw new \RuntimeException('Failed to insert test patient');
}
return $internalPID;
}
}
<?php
namespace Tests\Support\Traits;
use App\Models\Patient\PatientModel;
use Faker\Factory;
trait CreatesPatients
{
protected function createTestPatient(array $overrides = []): int
{
$faker = Factory::create('id_ID');
$patientPayload = array_merge([
'PatientID' => 'PAT' . $faker->numerify('##########'),
'AlternatePID' => 'ALT' . $faker->numerify('##########'),
'Prefix' => $faker->title,
'NameFirst' => 'Test',
'NameMiddle' => $faker->firstName,
'NameLast' => 'Patient',
'Suffix' => 'S.Kom',
'Sex' => (string) $faker->numberBetween(5, 6),
'PlaceOfBirth' => $faker->city,
'Birthdate' => $faker->date('Y-m-d'),
'ZIP' => $faker->postcode,
'Street_1' => $faker->streetAddress,
'City' => $faker->city,
'Province' => $faker->state,
'EmailAddress1' => 'test.' . $faker->unique()->userName . '@example.com',
'Phone' => $faker->numerify('08##########'),
'MobilePhone' => $faker->numerify('08##########'),
'Race' => (string) $faker->numberBetween(175, 205),
'Country' => (string) $faker->numberBetween(221, 469),
'MaritalStatus' => (string) $faker->numberBetween(8, 15),
'Religion' => (string) $faker->numberBetween(206, 212),
'Ethnic' => (string) $faker->numberBetween(213, 220),
'Citizenship' => 'WNI',
'isDead' => (string) $faker->numberBetween(0, 1),
'PatIdt' => [
'IdentifierType' => 'ID',
'Identifier' => $faker->numerify('################')
],
'PatAtt' => [
[ 'Address' => '/api/upload/' . $faker->uuid . '.jpg' ]
],
'PatCom' => $faker->sentence,
], $overrides);
if ($patientPayload['isDead'] === '1') {
$patientPayload['DeathDateTime'] = $faker->date('Y-m-d H:i:s');
} else {
$patientPayload['DeathDateTime'] = null;
}
$patientModel = new PatientModel();
$internalPID = $patientModel->createPatient($patientPayload);
if (!$internalPID) {
throw new \RuntimeException('Failed to insert test patient');
}
return $internalPID;
}
}

View File

@ -1,117 +1,117 @@
<?php
namespace Tests\Feature\Audit;
use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
use Firebase\JWT\JWT;
class AuditLogTest extends CIUnitTestCase
{
use FeatureTestTrait;
protected $db;
private $testRecId = 'TEST-REC-123';
protected function setUp(): void
{
parent::setUp();
$this->db = \Config\Database::connect();
$this->db->table('logpatient')->insert([
'TblName' => 'patient',
'RecID' => $this->testRecId,
'UserID' => 'USR_TEST',
'SiteID' => 'SITE01',
'SessionID' => 'sess_test',
'AppID' => 'clqms-api',
'EventID' => 'PATIENT_REGISTERED',
'ActivityID' => 'CREATE',
'LogDate' => '2026-03-25 12:00:00',
'Context' => json_encode([
'request_id' => 'req-test-1',
'route' => 'POST /api/patient',
'timestamp_utc' => '2026-03-25T12:00:00.000Z',
'entity_type' => 'patient',
'entity_version' => 1,
]),
]);
}
protected function tearDown(): void
{
$this->db->table('logpatient')->where('RecID', $this->testRecId)->delete();
parent::tearDown();
}
public function testTableIsRequired()
{
$result = $this->getWithAuth('api/audit-logs');
$result->assertStatus(400);
$result->assertJSONFragment([
'status' => 'failed',
'message' => 'table parameter is required',
]);
}
public function testUnknownTableReturnsValidationError()
{
$result = $this->getWithAuth('api/audit-logs?table=unknown');
$result->assertStatus(400);
$result->assertJSONFragment([
'status' => 'failed',
'message' => 'Unknown audit table: unknown',
]);
}
public function testAuditLogsFilterByRecId()
{
$result = $this->getWithAuth('api/audit-logs?table=logpatient&rec_id=' . $this->testRecId);
$result->assertStatus(200);
$result->assertJSONFragment([
'status' => 'success',
]);
$payload = json_decode($result->getJSON(), true);
$this->assertCount(1, $payload['data']['data']);
$this->assertEquals($this->testRecId, $payload['data']['data'][0]['RecID']);
$pagination = $payload['data']['pagination'];
$this->assertSame(1, $pagination['page']);
$this->assertSame(20, $pagination['perPage']);
$this->assertSame(1, $pagination['total']);
}
private function getWithAuth(string $uri)
{
$_COOKIE['token'] = $this->buildToken();
$response = $this->get($uri);
unset($_COOKIE['token']);
return $response;
}
private function buildToken(): string
{
$payload = [
'sub' => 'audit-test',
'iat' => time(),
];
return JWT::encode($payload, $this->resolveSecret(), 'HS256');
}
private function resolveSecret(): string
{
$secret = getenv('JWT_SECRET');
if ($secret === false) {
return 'tests-secret';
}
return trim($secret, "'\"");
}
}
<?php
namespace Tests\Feature\Audit;
use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
use Firebase\JWT\JWT;
class AuditLogTest extends CIUnitTestCase
{
use FeatureTestTrait;
protected $db;
private $testRecId = 'TEST-REC-123';
protected function setUp(): void
{
parent::setUp();
$this->db = \Config\Database::connect();
$this->db->table('logpatient')->insert([
'TblName' => 'patient',
'RecID' => $this->testRecId,
'UserID' => 'USR_TEST',
'SiteID' => 'SITE01',
'SessionID' => 'sess_test',
'AppID' => 'clqms-api',
'EventID' => 'PATIENT_REGISTERED',
'ActivityID' => 'CREATE',
'LogDate' => '2026-03-25 12:00:00',
'Context' => json_encode([
'request_id' => 'req-test-1',
'route' => 'POST /api/patient',
'timestamp_utc' => '2026-03-25T12:00:00.000Z',
'entity_type' => 'patient',
'entity_version' => 1,
]),
]);
}
protected function tearDown(): void
{
$this->db->table('logpatient')->where('RecID', $this->testRecId)->delete();
parent::tearDown();
}
public function testTableIsRequired()
{
$result = $this->getWithAuth('api/audit-logs');
$result->assertStatus(400);
$result->assertJSONFragment([
'status' => 'failed',
'message' => 'table parameter is required',
]);
}
public function testUnknownTableReturnsValidationError()
{
$result = $this->getWithAuth('api/audit-logs?table=unknown');
$result->assertStatus(400);
$result->assertJSONFragment([
'status' => 'failed',
'message' => 'Unknown audit table: unknown',
]);
}
public function testAuditLogsFilterByRecId()
{
$result = $this->getWithAuth('api/audit-logs?table=logpatient&rec_id=' . $this->testRecId);
$result->assertStatus(200);
$result->assertJSONFragment([
'status' => 'success',
]);
$payload = json_decode($result->getJSON(), true);
$this->assertCount(1, $payload['data']['data']);
$this->assertEquals($this->testRecId, $payload['data']['data'][0]['RecID']);
$pagination = $payload['data']['pagination'];
$this->assertSame(1, $pagination['page']);
$this->assertSame(20, $pagination['perPage']);
$this->assertSame(1, $pagination['total']);
}
private function getWithAuth(string $uri)
{
$_COOKIE['token'] = $this->buildToken();
$response = $this->get($uri);
unset($_COOKIE['token']);
return $response;
}
private function buildToken(): string
{
$payload = [
'sub' => 'audit-test',
'iat' => time(),
];
return JWT::encode($payload, $this->resolveSecret(), 'HS256');
}
private function resolveSecret(): string
{
$secret = getenv('JWT_SECRET');
if ($secret === false) {
return 'tests-secret';
}
return trim($secret, "'\"");
}
}

View File

@ -156,6 +156,7 @@ class ContactPatchTest extends CIUnitTestCase
[
'ContactDetID' => $keepDetail['ContactDetID'],
'JobTitle' => 'Senior Doctor',
'ContactStartDate' => '2026-04-16T07:22:44.000Z',
],
],
'created' => [
@ -182,8 +183,19 @@ class ContactPatchTest extends CIUnitTestCase
$this->assertCount(2, $afterData['Details']);
$detailIds = array_column($afterData['Details'], 'ContactDetID');
$this->assertContains($keepDetail['ContactDetID'], $detailIds);
$this->assertNotContains($deleteDetail['ContactDetID'], $detailIds);
$updatedDetails = array_values(array_filter($afterData['Details'], static fn ($row) => $row['ContactDetID'] === $keepDetail['ContactDetID']));
$updatedDetails = array_values(array_filter(
$afterData['Details'],
static fn ($row) => $row['ContactDetID'] === $keepDetail['ContactDetID']
));
$this->assertNotEmpty($updatedDetails);
$this->assertSame('Senior Doctor', $updatedDetails[0]['JobTitle']);
$createdDetails = array_values(array_filter(
$afterData['Details'],
static fn ($row) => (string) $row['SiteID'] === '3'
));
$this->assertNotEmpty($createdDetails);
}
}

View File

@ -1,144 +1,144 @@
<?php
namespace Tests\Feature\Location;
use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
use Firebase\JWT\JWT;
class LocationPatchTest extends CIUnitTestCase
{
use FeatureTestTrait;
protected string $token;
protected string $endpoint = 'api/location';
protected function setUp(): void
{
parent::setUp();
$key = getenv('JWT_SECRET') ?: 'my-secret-key';
$payload = [
'iss' => 'localhost',
'aud' => 'localhost',
'iat' => time(),
'nbf' => time(),
'exp' => time() + 3600,
'uid' => 1,
'email' => 'admin@admin.com',
];
$this->token = JWT::encode($payload, $key, 'HS256');
}
private function authHeaders(): array
{
return ['Cookie' => 'token=' . $this->token];
}
private function createLocation(array $data = []): array
{
$payload = array_merge([
'LocCode' => 'LC' . substr(uniqid(), -4),
'LocFull' => 'Test Location ' . uniqid(),
], $data);
$response = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('post', $this->endpoint, $payload);
$response->assertStatus(201);
$decoded = json_decode($response->getJSON(), true);
$locationId = $decoded['data']['LocationID'];
$show = $this->withHeaders($this->authHeaders())
->call('get', "{$this->endpoint}/{$locationId}");
$show->assertStatus(200);
$showData = json_decode($show->getJSON(), true)['data'];
return $showData;
}
public function testPartialUpdateLocationSuccess()
{
$location = $this->createLocation();
$id = $location['LocationID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['LocFull' => 'Updated Location']);
$patch->assertStatus(200);
$patchData = json_decode($patch->getJSON(), true);
$this->assertEquals('success', $patchData['status']);
$show = $this->withHeaders($this->authHeaders())->call('get', "{$this->endpoint}/{$id}");
$show->assertStatus(200);
$showData = json_decode($show->getJSON(), true)['data'];
$this->assertEquals('Updated Location', $showData['LocFull']);
$this->assertEquals($location['LocCode'], $showData['LocCode']);
}
public function testPartialUpdateLocationNotFound()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/999999", ['LocFull' => 'Updated']);
$patch->assertStatus(404);
}
public function testPartialUpdateLocationInvalidId()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/invalid", ['LocFull' => 'Updated']);
$patch->assertStatus(400);
}
public function testPartialUpdateLocationEmptyPayload()
{
$location = $this->createLocation();
$id = $location['LocationID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", []);
$patch->assertStatus(400);
}
public function testPartialUpdateLocationSingleField()
{
$location = $this->createLocation();
$id = $location['LocationID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['LocCode' => 'LC' . substr(uniqid(), -4)]);
$patch->assertStatus(200);
$showData = json_decode($this->withHeaders($this->authHeaders())
->call('get', "{$this->endpoint}/{$id}")
->getJSON(), true)['data'];
$this->assertNotEquals($location['LocCode'], $showData['LocCode']);
$this->assertEquals($location['LocFull'], $showData['LocFull']);
}
public function testPartialUpdateLocationAddressField()
{
$location = $this->createLocation();
$id = $location['LocationID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['Street1' => '123 Market St']);
$patch->assertStatus(200);
$showData = json_decode($this->withHeaders($this->authHeaders())
->call('get', "{$this->endpoint}/{$id}")
->getJSON(), true)['data'];
$this->assertEquals('123 Market St', $showData['Street1']);
}
}
<?php
namespace Tests\Feature\Location;
use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
use Firebase\JWT\JWT;
class LocationPatchTest extends CIUnitTestCase
{
use FeatureTestTrait;
protected string $token;
protected string $endpoint = 'api/location';
protected function setUp(): void
{
parent::setUp();
$key = getenv('JWT_SECRET') ?: 'my-secret-key';
$payload = [
'iss' => 'localhost',
'aud' => 'localhost',
'iat' => time(),
'nbf' => time(),
'exp' => time() + 3600,
'uid' => 1,
'email' => 'admin@admin.com',
];
$this->token = JWT::encode($payload, $key, 'HS256');
}
private function authHeaders(): array
{
return ['Cookie' => 'token=' . $this->token];
}
private function createLocation(array $data = []): array
{
$payload = array_merge([
'LocCode' => 'LC' . substr(uniqid(), -4),
'LocFull' => 'Test Location ' . uniqid(),
], $data);
$response = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('post', $this->endpoint, $payload);
$response->assertStatus(201);
$decoded = json_decode($response->getJSON(), true);
$locationId = $decoded['data']['LocationID'];
$show = $this->withHeaders($this->authHeaders())
->call('get', "{$this->endpoint}/{$locationId}");
$show->assertStatus(200);
$showData = json_decode($show->getJSON(), true)['data'];
return $showData;
}
public function testPartialUpdateLocationSuccess()
{
$location = $this->createLocation();
$id = $location['LocationID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['LocFull' => 'Updated Location']);
$patch->assertStatus(200);
$patchData = json_decode($patch->getJSON(), true);
$this->assertEquals('success', $patchData['status']);
$show = $this->withHeaders($this->authHeaders())->call('get', "{$this->endpoint}/{$id}");
$show->assertStatus(200);
$showData = json_decode($show->getJSON(), true)['data'];
$this->assertEquals('Updated Location', $showData['LocFull']);
$this->assertEquals($location['LocCode'], $showData['LocCode']);
}
public function testPartialUpdateLocationNotFound()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/999999", ['LocFull' => 'Updated']);
$patch->assertStatus(404);
}
public function testPartialUpdateLocationInvalidId()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/invalid", ['LocFull' => 'Updated']);
$patch->assertStatus(400);
}
public function testPartialUpdateLocationEmptyPayload()
{
$location = $this->createLocation();
$id = $location['LocationID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", []);
$patch->assertStatus(400);
}
public function testPartialUpdateLocationSingleField()
{
$location = $this->createLocation();
$id = $location['LocationID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['LocCode' => 'LC' . substr(uniqid(), -4)]);
$patch->assertStatus(200);
$showData = json_decode($this->withHeaders($this->authHeaders())
->call('get', "{$this->endpoint}/{$id}")
->getJSON(), true)['data'];
$this->assertNotEquals($location['LocCode'], $showData['LocCode']);
$this->assertEquals($location['LocFull'], $showData['LocFull']);
}
public function testPartialUpdateLocationAddressField()
{
$location = $this->createLocation();
$id = $location['LocationID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['Street1' => '123 Market St']);
$patch->assertStatus(200);
$showData = json_decode($this->withHeaders($this->authHeaders())
->call('get', "{$this->endpoint}/{$id}")
->getJSON(), true)['data'];
$this->assertEquals('123 Market St', $showData['Street1']);
}
}

View File

@ -1,301 +1,301 @@
<?php
namespace Tests\Feature;
use CodeIgniter\Exceptions\PageNotFoundException;
use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
use Firebase\JWT\JWT;
class MasterDataPatchTest extends CIUnitTestCase
{
use FeatureTestTrait;
protected string $token;
protected function setUp(): void
{
parent::setUp();
$key = getenv('JWT_SECRET') ?: 'my-secret-key';
$payload = [
'iss' => 'localhost',
'aud' => 'localhost',
'iat' => time(),
'nbf' => time(),
'exp' => time() + 3600,
'uid' => 1,
'email' => 'admin@admin.com',
];
$this->token = JWT::encode($payload, $key, 'HS256');
}
private function authHeaders(): array
{
return ['Cookie' => 'token=' . $this->token];
}
private function createResource(string $endpoint, array $payload)
{
$response = $this->withHeaders($this->authHeaders())
->withBody(json_encode($payload))
->call('post', $endpoint);
$response->assertStatus(201);
$decoded = json_decode($response->getJSON(), true);
$this->assertEquals('success', $decoded['status']);
return $decoded['data'];
}
private function fetchFirstRecord(string $endpoint, string $idKey): array
{
try {
$response = $this->withHeaders($this->authHeaders())->call('get', $endpoint);
} catch (PageNotFoundException $e) {
$this->markTestSkipped("{$endpoint} GET not available: {$e->getMessage()}");
}
$response->assertStatus(200);
$decoded = json_decode($response->getJSON(), true);
$rows = $decoded['data'] ?? [];
if (empty($rows)) {
$this->markTestSkipped("No data available at {$endpoint}");
}
$record = $rows[0];
$this->assertArrayHasKey($idKey, $record);
return $record;
}
private function fetchResource(string $endpoint, $id): array
{
try {
$response = $this->withHeaders($this->authHeaders())
->call('get', "$endpoint/{$id}");
} catch (PageNotFoundException $e) {
$this->markTestSkipped("{$endpoint}/{$id} GET not available: {$e->getMessage()}");
}
$response->assertStatus(200);
$decoded = json_decode($response->getJSON(), true);
return $decoded['data'];
}
public function testPartialUpdateOccupation()
{
$occCode = 'PATCH_OCC_' . uniqid();
$id = $this->createResource('api/occupation', [
'OccCode' => $occCode,
'OccText' => 'Original text',
]);
$originalData = $this->fetchResource('api/occupation', $id);
$originalCode = $originalData['OccCode'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "api/occupation/{$id}", ['OccText' => 'Patched occupation']);
$patch->assertStatus(201);
$patchData = json_decode($patch->getJSON(), true);
$this->assertEquals('success', $patchData['status']);
$showData = $this->fetchResource('api/occupation', $id);
$this->assertEquals('Patched occupation', $showData['OccText']);
$this->assertEquals($originalCode, $showData['OccCode']);
}
public function testPartialUpdateMedicalSpecialty()
{
$text = 'Specialty ' . uniqid();
$id = $this->createResource('api/medicalspecialty', ['SpecialtyText' => $text]);
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "api/medicalspecialty/{$id}", ['SpecialtyText' => 'Updated specialty']);
$patch->assertStatus(201);
$show = $this->withHeaders($this->authHeaders())->call('get', "api/medicalspecialty/{$id}");
$show->assertStatus(200);
$showData = json_decode($show->getJSON(), true)['data'];
$this->assertEquals('Updated specialty', $showData['SpecialtyText']);
}
public function testPartialUpdateCounter()
{
$initial = 'Counter ' . uniqid();
$id = $this->createResource('api/counter', [
'CounterName' => $initial,
'CounterValue' => 1,
'CounterStart' => 1,
'CounterEnd' => 10,
'CounterReset' => 1,
]);
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "api/counter/{$id}", ['CounterName' => 'Updated counter']);
$patch->assertStatus(201);
$show = $this->withHeaders($this->authHeaders())->call('get', "api/counter/{$id}");
$show->assertStatus(200);
$showData = json_decode($show->getJSON(), true)['data'];
$this->assertEquals('Updated counter', $showData['CounterName']);
$this->assertEquals(1, (int) $showData['CounterValue']);
}
public function testPartialUpdateOrganizationAccount()
{
$name = 'Account ' . uniqid();
$id = $this->createResource('api/organization/account', ['AccountName' => $name]);
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "api/organization/account/{$id}", ['AccountName' => 'Updated account']);
$patch->assertStatus(200);
$show = $this->withHeaders($this->authHeaders())->call('get', "api/organization/account/{$id}");
$show->assertStatus(200);
$showData = json_decode($show->getJSON(), true)['data'];
$this->assertEquals('Updated account', $showData['AccountName']);
}
public function testPartialUpdateDiscipline()
{
$code = 'DIS_' . strtoupper(bin2hex(random_bytes(2)));
$name = 'Discipline ' . uniqid();
$id = $this->createResource('api/organization/discipline', [
'DisciplineCode' => $code,
'DisciplineName' => $name,
]);
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "api/organization/discipline/{$id}", ['DisciplineName' => 'Discipline Updated']);
$patch->assertStatus(201);
$show = $this->withHeaders($this->authHeaders())->call('get', "api/organization/discipline/{$id}");
$show->assertStatus(200);
$showData = json_decode($show->getJSON(), true)['data'];
$this->assertEquals('Discipline Updated', $showData['DisciplineName']);
$this->assertEquals($code, $showData['DisciplineCode']);
}
public function testPartialUpdateCodingSystem()
{
$abbr = 'CS' . strtoupper(bin2hex(random_bytes(2)));
$full = 'Full text ' . uniqid();
$id = $this->createResource('api/organization/codingsys', [
'CodingSysAbb' => $abbr,
'FullText' => $full,
]);
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "api/organization/codingsys/{$id}", ['FullText' => 'Updated full text']);
$patch->assertStatus(201);
$show = $this->withHeaders($this->authHeaders())->call('get', "api/organization/codingsys/{$id}");
$show->assertStatus(200);
$showData = json_decode($show->getJSON(), true)['data'];
$this->assertEquals('Updated full text', $showData['FullText']);
$this->assertEquals($abbr, $showData['CodingSysAbb']);
}
public function testPartialUpdateSpecimenContainer()
{
$record = $this->fetchFirstRecord('api/specimen/container', 'ConDefID');
$id = $record['ConDefID'];
$newName = 'Patch Container ' . uniqid();
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "api/specimen/container/{$id}", ['ConName' => $newName]);
$patch->assertStatus(201);
$showData = $this->fetchResource('api/specimen/container', $id);
$this->assertEquals($newName, $showData['ConName']);
$this->assertEquals($record['ConCode'] ?? null, $showData['ConCode'] ?? null);
}
public function testPartialUpdateSpecimenPrep()
{
$record = $this->fetchFirstRecord('api/specimen/prep', 'SpcPrpID');
$id = $record['SpcPrpID'];
$newDesc = 'Partial Prep ' . uniqid();
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "api/specimen/prep/{$id}", ['Description' => $newDesc]);
$patch->assertStatus(201);
$showData = $this->fetchResource('api/specimen/prep', $id);
$this->assertEquals($newDesc, $showData['Description']);
$this->assertEquals($record['SpcStaID'] ?? null, $showData['SpcStaID'] ?? null);
}
public function testPartialUpdateSpecimenStatus()
{
$record = $this->fetchFirstRecord('api/specimen/status', 'SpcStaID');
$id = $record['SpcStaID'];
$newStatus = 'UpdatedStatus';
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "api/specimen/status/{$id}", ['SpcStatus' => $newStatus]);
$patch->assertStatus(201);
$showData = $this->fetchResource('api/specimen/status', $id);
$this->assertEquals($newStatus, $showData['SpcStatus']);
$this->assertEquals($record['OrderID'] ?? null, $showData['OrderID'] ?? null);
}
public function testPartialUpdateSpecimenCollection()
{
$record = $this->fetchFirstRecord('api/specimen/collection', 'SpcColID');
$id = $record['SpcColID'];
$newBodySite = 'BodySite ' . uniqid();
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "api/specimen/collection/{$id}", ['BodySite' => $newBodySite]);
$patch->assertStatus(201);
$showData = $this->fetchResource('api/specimen/collection', $id);
$this->assertEquals($newBodySite, $showData['BodySite']);
$this->assertEquals($record['SpRole'] ?? null, $showData['SpRole'] ?? null);
}
public function testPartialUpdateEquipmentList()
{
$record = $this->fetchFirstRecord('api/equipmentlist', 'EID');
$id = $record['EID'];
$newName = 'Equipment ' . uniqid();
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "api/equipmentlist/{$id}", ['InstrumentName' => $newName]);
$patch->assertStatus(200, 'Equipment patch should return 200');
$showData = $this->fetchResource('api/equipmentlist', $id);
$this->assertEquals($newName, $showData['InstrumentName']);
$this->assertEquals($record['DepartmentID'] ?? null, $showData['DepartmentID'] ?? null);
}
}
<?php
namespace Tests\Feature;
use CodeIgniter\Exceptions\PageNotFoundException;
use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
use Firebase\JWT\JWT;
class MasterDataPatchTest extends CIUnitTestCase
{
use FeatureTestTrait;
protected string $token;
protected function setUp(): void
{
parent::setUp();
$key = getenv('JWT_SECRET') ?: 'my-secret-key';
$payload = [
'iss' => 'localhost',
'aud' => 'localhost',
'iat' => time(),
'nbf' => time(),
'exp' => time() + 3600,
'uid' => 1,
'email' => 'admin@admin.com',
];
$this->token = JWT::encode($payload, $key, 'HS256');
}
private function authHeaders(): array
{
return ['Cookie' => 'token=' . $this->token];
}
private function createResource(string $endpoint, array $payload)
{
$response = $this->withHeaders($this->authHeaders())
->withBody(json_encode($payload))
->call('post', $endpoint);
$response->assertStatus(201);
$decoded = json_decode($response->getJSON(), true);
$this->assertEquals('success', $decoded['status']);
return $decoded['data'];
}
private function fetchFirstRecord(string $endpoint, string $idKey): array
{
try {
$response = $this->withHeaders($this->authHeaders())->call('get', $endpoint);
} catch (PageNotFoundException $e) {
$this->markTestSkipped("{$endpoint} GET not available: {$e->getMessage()}");
}
$response->assertStatus(200);
$decoded = json_decode($response->getJSON(), true);
$rows = $decoded['data'] ?? [];
if (empty($rows)) {
$this->markTestSkipped("No data available at {$endpoint}");
}
$record = $rows[0];
$this->assertArrayHasKey($idKey, $record);
return $record;
}
private function fetchResource(string $endpoint, $id): array
{
try {
$response = $this->withHeaders($this->authHeaders())
->call('get', "$endpoint/{$id}");
} catch (PageNotFoundException $e) {
$this->markTestSkipped("{$endpoint}/{$id} GET not available: {$e->getMessage()}");
}
$response->assertStatus(200);
$decoded = json_decode($response->getJSON(), true);
return $decoded['data'];
}
public function testPartialUpdateOccupation()
{
$occCode = 'PATCH_OCC_' . uniqid();
$id = $this->createResource('api/occupation', [
'OccCode' => $occCode,
'OccText' => 'Original text',
]);
$originalData = $this->fetchResource('api/occupation', $id);
$originalCode = $originalData['OccCode'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "api/occupation/{$id}", ['OccText' => 'Patched occupation']);
$patch->assertStatus(201);
$patchData = json_decode($patch->getJSON(), true);
$this->assertEquals('success', $patchData['status']);
$showData = $this->fetchResource('api/occupation', $id);
$this->assertEquals('Patched occupation', $showData['OccText']);
$this->assertEquals($originalCode, $showData['OccCode']);
}
public function testPartialUpdateMedicalSpecialty()
{
$text = 'Specialty ' . uniqid();
$id = $this->createResource('api/medicalspecialty', ['SpecialtyText' => $text]);
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "api/medicalspecialty/{$id}", ['SpecialtyText' => 'Updated specialty']);
$patch->assertStatus(201);
$show = $this->withHeaders($this->authHeaders())->call('get', "api/medicalspecialty/{$id}");
$show->assertStatus(200);
$showData = json_decode($show->getJSON(), true)['data'];
$this->assertEquals('Updated specialty', $showData['SpecialtyText']);
}
public function testPartialUpdateCounter()
{
$initial = 'Counter ' . uniqid();
$id = $this->createResource('api/counter', [
'CounterName' => $initial,
'CounterValue' => 1,
'CounterStart' => 1,
'CounterEnd' => 10,
'CounterReset' => 1,
]);
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "api/counter/{$id}", ['CounterName' => 'Updated counter']);
$patch->assertStatus(201);
$show = $this->withHeaders($this->authHeaders())->call('get', "api/counter/{$id}");
$show->assertStatus(200);
$showData = json_decode($show->getJSON(), true)['data'];
$this->assertEquals('Updated counter', $showData['CounterName']);
$this->assertEquals(1, (int) $showData['CounterValue']);
}
public function testPartialUpdateOrganizationAccount()
{
$name = 'Account ' . uniqid();
$id = $this->createResource('api/organization/account', ['AccountName' => $name]);
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "api/organization/account/{$id}", ['AccountName' => 'Updated account']);
$patch->assertStatus(200);
$show = $this->withHeaders($this->authHeaders())->call('get', "api/organization/account/{$id}");
$show->assertStatus(200);
$showData = json_decode($show->getJSON(), true)['data'];
$this->assertEquals('Updated account', $showData['AccountName']);
}
public function testPartialUpdateDiscipline()
{
$code = 'DIS_' . strtoupper(bin2hex(random_bytes(2)));
$name = 'Discipline ' . uniqid();
$id = $this->createResource('api/organization/discipline', [
'DisciplineCode' => $code,
'DisciplineName' => $name,
]);
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "api/organization/discipline/{$id}", ['DisciplineName' => 'Discipline Updated']);
$patch->assertStatus(201);
$show = $this->withHeaders($this->authHeaders())->call('get', "api/organization/discipline/{$id}");
$show->assertStatus(200);
$showData = json_decode($show->getJSON(), true)['data'];
$this->assertEquals('Discipline Updated', $showData['DisciplineName']);
$this->assertEquals($code, $showData['DisciplineCode']);
}
public function testPartialUpdateCodingSystem()
{
$abbr = 'CS' . strtoupper(bin2hex(random_bytes(2)));
$full = 'Full text ' . uniqid();
$id = $this->createResource('api/organization/codingsys', [
'CodingSysAbb' => $abbr,
'FullText' => $full,
]);
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "api/organization/codingsys/{$id}", ['FullText' => 'Updated full text']);
$patch->assertStatus(201);
$show = $this->withHeaders($this->authHeaders())->call('get', "api/organization/codingsys/{$id}");
$show->assertStatus(200);
$showData = json_decode($show->getJSON(), true)['data'];
$this->assertEquals('Updated full text', $showData['FullText']);
$this->assertEquals($abbr, $showData['CodingSysAbb']);
}
public function testPartialUpdateSpecimenContainer()
{
$record = $this->fetchFirstRecord('api/specimen/container', 'ConDefID');
$id = $record['ConDefID'];
$newName = 'Patch Container ' . uniqid();
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "api/specimen/container/{$id}", ['ConName' => $newName]);
$patch->assertStatus(201);
$showData = $this->fetchResource('api/specimen/container', $id);
$this->assertEquals($newName, $showData['ConName']);
$this->assertEquals($record['ConCode'] ?? null, $showData['ConCode'] ?? null);
}
public function testPartialUpdateSpecimenPrep()
{
$record = $this->fetchFirstRecord('api/specimen/prep', 'SpcPrpID');
$id = $record['SpcPrpID'];
$newDesc = 'Partial Prep ' . uniqid();
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "api/specimen/prep/{$id}", ['Description' => $newDesc]);
$patch->assertStatus(201);
$showData = $this->fetchResource('api/specimen/prep', $id);
$this->assertEquals($newDesc, $showData['Description']);
$this->assertEquals($record['SpcStaID'] ?? null, $showData['SpcStaID'] ?? null);
}
public function testPartialUpdateSpecimenStatus()
{
$record = $this->fetchFirstRecord('api/specimen/status', 'SpcStaID');
$id = $record['SpcStaID'];
$newStatus = 'UpdatedStatus';
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "api/specimen/status/{$id}", ['SpcStatus' => $newStatus]);
$patch->assertStatus(201);
$showData = $this->fetchResource('api/specimen/status', $id);
$this->assertEquals($newStatus, $showData['SpcStatus']);
$this->assertEquals($record['OrderID'] ?? null, $showData['OrderID'] ?? null);
}
public function testPartialUpdateSpecimenCollection()
{
$record = $this->fetchFirstRecord('api/specimen/collection', 'SpcColID');
$id = $record['SpcColID'];
$newBodySite = 'BodySite ' . uniqid();
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "api/specimen/collection/{$id}", ['BodySite' => $newBodySite]);
$patch->assertStatus(201);
$showData = $this->fetchResource('api/specimen/collection', $id);
$this->assertEquals($newBodySite, $showData['BodySite']);
$this->assertEquals($record['SpRole'] ?? null, $showData['SpRole'] ?? null);
}
public function testPartialUpdateEquipmentList()
{
$record = $this->fetchFirstRecord('api/equipmentlist', 'EID');
$id = $record['EID'];
$newName = 'Equipment ' . uniqid();
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "api/equipmentlist/{$id}", ['InstrumentName' => $newName]);
$patch->assertStatus(200, 'Equipment patch should return 200');
$showData = $this->fetchResource('api/equipmentlist', $id);
$this->assertEquals($newName, $showData['InstrumentName']);
$this->assertEquals($record['DepartmentID'] ?? null, $showData['DepartmentID'] ?? null);
}
}

View File

@ -1,121 +1,121 @@
<?php
namespace Tests\Feature\OrderTest;
use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
use Firebase\JWT\JWT;
class OrderTestPatchTest extends CIUnitTestCase
{
use FeatureTestTrait;
protected string $token;
protected string $endpoint = 'api/ordertest';
protected function setUp(): void
{
parent::setUp();
$key = getenv('JWT_SECRET') ?: 'my-secret-key';
$payload = [
'iss' => 'localhost',
'aud' => 'localhost',
'iat' => time(),
'nbf' => time(),
'exp' => time() + 3600,
'uid' => 1,
'email' => 'admin@admin.com',
];
$this->token = JWT::encode($payload, $key, 'HS256');
}
private function authHeaders(): array
{
return ['Cookie' => 'token=' . $this->token];
}
private function createOrderTest(array $data = []): array
{
$payload = array_merge([
'OrderCode' => 'ORD_' . uniqid(),
'OrderName' => 'Test Order ' . uniqid(),
], $data);
$response = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('post', $this->endpoint, $payload);
$response->assertStatus(201);
$decoded = json_decode($response->getJSON(), true);
return $decoded['data'];
}
public function testPartialUpdateOrderTestSuccess()
{
$order = $this->createOrderTest();
$id = $order['OrderID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['OrderName' => 'Updated Order']);
$patch->assertStatus(200);
$patchData = json_decode($patch->getJSON(), true);
$this->assertEquals('success', $patchData['status']);
$show = $this->withHeaders($this->authHeaders())->call('get', "{$this->endpoint}/{$id}");
$show->assertStatus(200);
$showData = json_decode($show->getJSON(), true)['data'];
$this->assertEquals('Updated Order', $showData['OrderName']);
$this->assertEquals($order['OrderCode'], $showData['OrderCode']);
}
public function testPartialUpdateOrderTestNotFound()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/999999", ['OrderName' => 'Updated']);
$patch->assertStatus(404);
}
public function testPartialUpdateOrderTestInvalidId()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/invalid", ['OrderName' => 'Updated']);
$patch->assertStatus(400);
}
public function testPartialUpdateOrderTestEmptyPayload()
{
$order = $this->createOrderTest();
$id = $order['OrderID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", []);
$patch->assertStatus(400);
}
public function testPartialUpdateOrderTestSingleField()
{
$order = $this->createOrderTest();
$id = $order['OrderID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['OrderCode' => 'NEW_' . uniqid()]);
$patch->assertStatus(200);
$showData = json_decode($this->withHeaders($this->authHeaders())
->call('get', "{$this->endpoint}/{$id}")
->getJSON(), true)['data'];
$this->assertNotEquals($order['OrderCode'], $showData['OrderCode']);
$this->assertEquals($order['OrderName'], $showData['OrderName']);
}
}
<?php
namespace Tests\Feature\OrderTest;
use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
use Firebase\JWT\JWT;
class OrderTestPatchTest extends CIUnitTestCase
{
use FeatureTestTrait;
protected string $token;
protected string $endpoint = 'api/ordertest';
protected function setUp(): void
{
parent::setUp();
$key = getenv('JWT_SECRET') ?: 'my-secret-key';
$payload = [
'iss' => 'localhost',
'aud' => 'localhost',
'iat' => time(),
'nbf' => time(),
'exp' => time() + 3600,
'uid' => 1,
'email' => 'admin@admin.com',
];
$this->token = JWT::encode($payload, $key, 'HS256');
}
private function authHeaders(): array
{
return ['Cookie' => 'token=' . $this->token];
}
private function createOrderTest(array $data = []): array
{
$payload = array_merge([
'OrderCode' => 'ORD_' . uniqid(),
'OrderName' => 'Test Order ' . uniqid(),
], $data);
$response = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('post', $this->endpoint, $payload);
$response->assertStatus(201);
$decoded = json_decode($response->getJSON(), true);
return $decoded['data'];
}
public function testPartialUpdateOrderTestSuccess()
{
$order = $this->createOrderTest();
$id = $order['OrderID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['OrderName' => 'Updated Order']);
$patch->assertStatus(200);
$patchData = json_decode($patch->getJSON(), true);
$this->assertEquals('success', $patchData['status']);
$show = $this->withHeaders($this->authHeaders())->call('get', "{$this->endpoint}/{$id}");
$show->assertStatus(200);
$showData = json_decode($show->getJSON(), true)['data'];
$this->assertEquals('Updated Order', $showData['OrderName']);
$this->assertEquals($order['OrderCode'], $showData['OrderCode']);
}
public function testPartialUpdateOrderTestNotFound()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/999999", ['OrderName' => 'Updated']);
$patch->assertStatus(404);
}
public function testPartialUpdateOrderTestInvalidId()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/invalid", ['OrderName' => 'Updated']);
$patch->assertStatus(400);
}
public function testPartialUpdateOrderTestEmptyPayload()
{
$order = $this->createOrderTest();
$id = $order['OrderID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", []);
$patch->assertStatus(400);
}
public function testPartialUpdateOrderTestSingleField()
{
$order = $this->createOrderTest();
$id = $order['OrderID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['OrderCode' => 'NEW_' . uniqid()]);
$patch->assertStatus(200);
$showData = json_decode($this->withHeaders($this->authHeaders())
->call('get', "{$this->endpoint}/{$id}")
->getJSON(), true)['data'];
$this->assertNotEquals($order['OrderCode'], $showData['OrderCode']);
$this->assertEquals($order['OrderName'], $showData['OrderName']);
}
}

View File

@ -1,121 +1,121 @@
<?php
namespace Tests\Feature\Organization;
use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\FeatureTestTrait;
use Firebase\JWT\JWT;
class AccountPatchTest extends CIUnitTestCase
{
use FeatureTestTrait;
protected string $endpoint = 'api/organization/account';
protected string $token;
protected function setUp(): void
{
parent::setUp();
$key = getenv('JWT_SECRET') ?: 'my-secret-key';
$payload = [
'iss' => 'localhost',
'aud' => 'localhost',
'iat' => time(),
'nbf' => time(),
'exp' => time() + 3600,
'uid' => 1,
'email' => 'admin@admin.com',
];
$this->token = JWT::encode($payload, $key, 'HS256');
}
private function authHeaders(): array
{
return ['Cookie' => 'token=' . $this->token];
}
private function createAccount(array $data = []): int
{
$payload = array_merge([
'AccountName' => 'Account ' . uniqid(),
], $data);
$response = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('post', $this->endpoint, $payload);
$response->assertStatus(201);
$decoded = json_decode($response->getJSON(), true);
return $decoded['data'];
}
private function fetchAccount(int $id): array
{
$response = $this->withHeaders($this->authHeaders())->call('get', "{$this->endpoint}/{$id}");
$response->assertStatus(200);
$decoded = json_decode($response->getJSON(), true);
return $decoded['data'] ?? [];
}
public function testPartialUpdateAccountSuccess()
{
$id = $this->createAccount();
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['AccountName' => 'Updated Account']);
$patch->assertStatus(200);
$this->assertSame('success', json_decode($patch->getJSON(), true)['status']);
$account = $this->fetchAccount($id);
$this->assertEquals('Updated Account', $account['AccountName']);
$this->assertEquals($id, $account['AccountID']);
}
public function testPartialUpdateAccountNotFound()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/999999", ['AccountName' => 'Does not matter']);
$patch->assertStatus(404);
}
public function testPartialUpdateAccountInvalidId()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/invalid", ['AccountName' => 'Bad']);
$patch->assertStatus(400);
}
public function testPartialUpdateAccountEmptyPayload()
{
$id = $this->createAccount();
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", []);
$patch->assertStatus(400);
}
public function testPartialUpdateAccountSingleField()
{
$id = $this->createAccount(['AccountName' => 'Original Name']);
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['AccountName' => 'New Name']);
$patch->assertStatus(200);
$account = $this->fetchAccount($id);
$this->assertEquals('New Name', $account['AccountName']);
}
}
<?php
namespace Tests\Feature\Organization;
use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\FeatureTestTrait;
use Firebase\JWT\JWT;
class AccountPatchTest extends CIUnitTestCase
{
use FeatureTestTrait;
protected string $endpoint = 'api/organization/account';
protected string $token;
protected function setUp(): void
{
parent::setUp();
$key = getenv('JWT_SECRET') ?: 'my-secret-key';
$payload = [
'iss' => 'localhost',
'aud' => 'localhost',
'iat' => time(),
'nbf' => time(),
'exp' => time() + 3600,
'uid' => 1,
'email' => 'admin@admin.com',
];
$this->token = JWT::encode($payload, $key, 'HS256');
}
private function authHeaders(): array
{
return ['Cookie' => 'token=' . $this->token];
}
private function createAccount(array $data = []): int
{
$payload = array_merge([
'AccountName' => 'Account ' . uniqid(),
], $data);
$response = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('post', $this->endpoint, $payload);
$response->assertStatus(201);
$decoded = json_decode($response->getJSON(), true);
return $decoded['data'];
}
private function fetchAccount(int $id): array
{
$response = $this->withHeaders($this->authHeaders())->call('get', "{$this->endpoint}/{$id}");
$response->assertStatus(200);
$decoded = json_decode($response->getJSON(), true);
return $decoded['data'] ?? [];
}
public function testPartialUpdateAccountSuccess()
{
$id = $this->createAccount();
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['AccountName' => 'Updated Account']);
$patch->assertStatus(200);
$this->assertSame('success', json_decode($patch->getJSON(), true)['status']);
$account = $this->fetchAccount($id);
$this->assertEquals('Updated Account', $account['AccountName']);
$this->assertEquals($id, $account['AccountID']);
}
public function testPartialUpdateAccountNotFound()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/999999", ['AccountName' => 'Does not matter']);
$patch->assertStatus(404);
}
public function testPartialUpdateAccountInvalidId()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/invalid", ['AccountName' => 'Bad']);
$patch->assertStatus(400);
}
public function testPartialUpdateAccountEmptyPayload()
{
$id = $this->createAccount();
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", []);
$patch->assertStatus(400);
}
public function testPartialUpdateAccountSingleField()
{
$id = $this->createAccount(['AccountName' => 'Original Name']);
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['AccountName' => 'New Name']);
$patch->assertStatus(200);
$account = $this->fetchAccount($id);
$this->assertEquals('New Name', $account['AccountName']);
}
}

View File

@ -1,31 +1,31 @@
<?php
namespace Tests\Feature\Organization;
use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
class CodingSysControllerTest extends CIUnitTestCase
{
use FeatureTestTrait;
protected $endpoint = 'api/organization/codingsys';
public function testIndexCodingSys()
{
$result = $this->get($this->endpoint);
$result->assertStatus(200);
}
public function testCreateCodingSys()
{
$payload = [
'CodingSysAbb' => 'ICD' . substr(time(), -3),
'FullText' => 'International Classification of Diseases 10 ' . time(),
'Description' => 'Medical diagnosis coding system'
];
$result = $this->withBodyFormat('json')->post($this->endpoint, $payload);
$result->assertStatus(201);
}
}
<?php
namespace Tests\Feature\Organization;
use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
class CodingSysControllerTest extends CIUnitTestCase
{
use FeatureTestTrait;
protected $endpoint = 'api/organization/codingsys';
public function testIndexCodingSys()
{
$result = $this->get($this->endpoint);
$result->assertStatus(200);
}
public function testCreateCodingSys()
{
$payload = [
'CodingSysAbb' => 'ICD' . substr(time(), -3),
'FullText' => 'International Classification of Diseases 10 ' . time(),
'Description' => 'Medical diagnosis coding system'
];
$result = $this->withBodyFormat('json')->post($this->endpoint, $payload);
$result->assertStatus(201);
}
}

View File

@ -1,123 +1,123 @@
<?php
namespace Tests\Feature\Organization;
use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
use Firebase\JWT\JWT;
class DepartmentPatchTest extends CIUnitTestCase
{
use FeatureTestTrait;
protected string $token;
protected string $endpoint = 'api/organization/department';
protected function setUp(): void
{
parent::setUp();
$key = getenv('JWT_SECRET') ?: 'my-secret-key';
$payload = [
'iss' => 'localhost',
'aud' => 'localhost',
'iat' => time(),
'nbf' => time(),
'exp' => time() + 3600,
'uid' => 1,
'email' => 'admin@admin.com',
];
$this->token = JWT::encode($payload, $key, 'HS256');
}
private function authHeaders(): array
{
return ['Cookie' => 'token=' . $this->token];
}
private function createDepartment(array $data = []): array
{
$payload = array_merge([
'DepartmentCode' => 'DEPT_' . uniqid(),
'DepartmentName' => 'Test Department ' . uniqid(),
], $data);
$response = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('post', $this->endpoint, $payload);
if ($response->getStatusCode() !== 201) {
$this->markTestSkipped('Failed to create test department');
}
$decoded = json_decode($response->getJSON(), true);
return $decoded['data'] ?? [];
}
public function testPartialUpdateDepartmentSuccess()
{
$dept = $this->createDepartment();
$id = $dept['DepartmentID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['DepartmentName' => 'Updated Department']);
$patch->assertStatus(200);
$patchData = json_decode($patch->getJSON(), true);
$this->assertEquals('success', $patchData['status']);
$show = $this->withHeaders($this->authHeaders())->call('get', "{$this->endpoint}/{$id}");
$show->assertStatus(200);
$showData = json_decode($show->getJSON(), true)['data'];
$this->assertEquals('Updated Department', $showData['DepartmentName']);
$this->assertEquals($dept['DepartmentCode'], $showData['DepartmentCode']);
}
public function testPartialUpdateDepartmentNotFound()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/999999", ['DepartmentName' => 'Updated']);
$this->assertTrue(in_array($patch->getStatusCode(), [404, 400, 201]));
}
public function testPartialUpdateDepartmentZeroId()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/0", ['DepartmentName' => 'Updated']);
$this->assertTrue(in_array($patch->getStatusCode(), [404, 400, 201]));
}
public function testPartialUpdateDepartmentEmptyPayload()
{
$dept = $this->createDepartment();
$id = $dept['DepartmentID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", []);
$patch->assertStatus(400);
}
public function testPartialUpdateDepartmentSingleField()
{
$dept = $this->createDepartment();
$id = $dept['DepartmentID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['DepartmentCode' => 'NEW_' . uniqid()]);
$patch->assertStatus(200);
$showData = json_decode($this->withHeaders($this->authHeaders())
->call('get', "{$this->endpoint}/{$id}")
->getJSON(), true)['data'];
$this->assertNotEquals($dept['DepartmentCode'], $showData['DepartmentCode']);
$this->assertEquals($dept['DepartmentName'], $showData['DepartmentName']);
}
}
<?php
namespace Tests\Feature\Organization;
use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
use Firebase\JWT\JWT;
class DepartmentPatchTest extends CIUnitTestCase
{
use FeatureTestTrait;
protected string $token;
protected string $endpoint = 'api/organization/department';
protected function setUp(): void
{
parent::setUp();
$key = getenv('JWT_SECRET') ?: 'my-secret-key';
$payload = [
'iss' => 'localhost',
'aud' => 'localhost',
'iat' => time(),
'nbf' => time(),
'exp' => time() + 3600,
'uid' => 1,
'email' => 'admin@admin.com',
];
$this->token = JWT::encode($payload, $key, 'HS256');
}
private function authHeaders(): array
{
return ['Cookie' => 'token=' . $this->token];
}
private function createDepartment(array $data = []): array
{
$payload = array_merge([
'DepartmentCode' => 'DEPT_' . uniqid(),
'DepartmentName' => 'Test Department ' . uniqid(),
], $data);
$response = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('post', $this->endpoint, $payload);
if ($response->getStatusCode() !== 201) {
$this->markTestSkipped('Failed to create test department');
}
$decoded = json_decode($response->getJSON(), true);
return $decoded['data'] ?? [];
}
public function testPartialUpdateDepartmentSuccess()
{
$dept = $this->createDepartment();
$id = $dept['DepartmentID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['DepartmentName' => 'Updated Department']);
$patch->assertStatus(200);
$patchData = json_decode($patch->getJSON(), true);
$this->assertEquals('success', $patchData['status']);
$show = $this->withHeaders($this->authHeaders())->call('get', "{$this->endpoint}/{$id}");
$show->assertStatus(200);
$showData = json_decode($show->getJSON(), true)['data'];
$this->assertEquals('Updated Department', $showData['DepartmentName']);
$this->assertEquals($dept['DepartmentCode'], $showData['DepartmentCode']);
}
public function testPartialUpdateDepartmentNotFound()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/999999", ['DepartmentName' => 'Updated']);
$this->assertTrue(in_array($patch->getStatusCode(), [404, 400, 201]));
}
public function testPartialUpdateDepartmentZeroId()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/0", ['DepartmentName' => 'Updated']);
$this->assertTrue(in_array($patch->getStatusCode(), [404, 400, 201]));
}
public function testPartialUpdateDepartmentEmptyPayload()
{
$dept = $this->createDepartment();
$id = $dept['DepartmentID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", []);
$patch->assertStatus(400);
}
public function testPartialUpdateDepartmentSingleField()
{
$dept = $this->createDepartment();
$id = $dept['DepartmentID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['DepartmentCode' => 'NEW_' . uniqid()]);
$patch->assertStatus(200);
$showData = json_decode($this->withHeaders($this->authHeaders())
->call('get', "{$this->endpoint}/{$id}")
->getJSON(), true)['data'];
$this->assertNotEquals($dept['DepartmentCode'], $showData['DepartmentCode']);
$this->assertEquals($dept['DepartmentName'], $showData['DepartmentName']);
}
}

View File

@ -1,121 +1,121 @@
<?php
namespace Tests\Feature\Organization;
use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\FeatureTestTrait;
use Firebase\JWT\JWT;
class DisciplinePatchTest extends CIUnitTestCase
{
use FeatureTestTrait;
protected string $endpoint = 'api/organization/discipline';
protected string $token;
protected function setUp(): void
{
parent::setUp();
$key = getenv('JWT_SECRET') ?: 'my-secret-key';
$payload = [
'iss' => 'localhost',
'aud' => 'localhost',
'iat' => time(),
'nbf' => time(),
'exp' => time() + 3600,
'uid' => 1,
'email' => 'admin@admin.com',
];
$this->token = JWT::encode($payload, $key, 'HS256');
}
private function authHeaders(): array
{
return ['Cookie' => 'token=' . $this->token];
}
private function createDiscipline(array $data = []): int
{
$payload = array_merge([
'DisciplineCode' => 'D' . strtoupper(bin2hex(random_bytes(1))),
'DisciplineName' => 'Discipline ' . uniqid(),
], $data);
$response = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('post', $this->endpoint, $payload);
$response->assertStatus(201);
$decoded = json_decode($response->getJSON(), true);
return $decoded['data'];
}
private function fetchDiscipline(int $id): array
{
$response = $this->withHeaders($this->authHeaders())->call('get', "{$this->endpoint}/{$id}");
$response->assertStatus(200);
$decoded = json_decode($response->getJSON(), true);
return $decoded['data'] ?? [];
}
public function testPartialUpdateDisciplineSuccess()
{
$id = $this->createDiscipline();
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['DisciplineName' => 'Updated Discipline']);
$patch->assertStatus(200);
$this->assertSame('success', json_decode($patch->getJSON(), true)['status']);
$discipline = $this->fetchDiscipline($id);
$this->assertEquals('Updated Discipline', $discipline['DisciplineName']);
$this->assertEquals($id, $discipline['DisciplineID']);
}
public function testPartialUpdateDisciplineNotFound()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/999999", ['DisciplineName' => 'Does not matter']);
$patch->assertStatus(404);
}
public function testPartialUpdateDisciplineInvalidId()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/invalid", ['DisciplineName' => 'Bad']);
$patch->assertStatus(400);
}
public function testPartialUpdateDisciplineEmptyPayload()
{
$id = $this->createDiscipline();
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", []);
$patch->assertStatus(400);
}
public function testPartialUpdateDisciplineSingleField()
{
$id = $this->createDiscipline(['DisciplineName' => 'Original Name']);
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['DisciplineName' => 'New Name']);
$patch->assertStatus(200);
$discipline = $this->fetchDiscipline($id);
$this->assertEquals('New Name', $discipline['DisciplineName']);
}
}
<?php
namespace Tests\Feature\Organization;
use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\FeatureTestTrait;
use Firebase\JWT\JWT;
class DisciplinePatchTest extends CIUnitTestCase
{
use FeatureTestTrait;
protected string $endpoint = 'api/organization/discipline';
protected string $token;
protected function setUp(): void
{
parent::setUp();
$key = getenv('JWT_SECRET') ?: 'my-secret-key';
$payload = [
'iss' => 'localhost',
'aud' => 'localhost',
'iat' => time(),
'nbf' => time(),
'exp' => time() + 3600,
'uid' => 1,
'email' => 'admin@admin.com',
];
$this->token = JWT::encode($payload, $key, 'HS256');
}
private function authHeaders(): array
{
return ['Cookie' => 'token=' . $this->token];
}
private function createDiscipline(array $data = []): int
{
$payload = array_merge([
'DisciplineCode' => 'D' . strtoupper(bin2hex(random_bytes(1))),
'DisciplineName' => 'Discipline ' . uniqid(),
], $data);
$response = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('post', $this->endpoint, $payload);
$response->assertStatus(201);
$decoded = json_decode($response->getJSON(), true);
return $decoded['data'];
}
private function fetchDiscipline(int $id): array
{
$response = $this->withHeaders($this->authHeaders())->call('get', "{$this->endpoint}/{$id}");
$response->assertStatus(200);
$decoded = json_decode($response->getJSON(), true);
return $decoded['data'] ?? [];
}
public function testPartialUpdateDisciplineSuccess()
{
$id = $this->createDiscipline();
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['DisciplineName' => 'Updated Discipline']);
$patch->assertStatus(200);
$this->assertSame('success', json_decode($patch->getJSON(), true)['status']);
$discipline = $this->fetchDiscipline($id);
$this->assertEquals('Updated Discipline', $discipline['DisciplineName']);
$this->assertEquals($id, $discipline['DisciplineID']);
}
public function testPartialUpdateDisciplineNotFound()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/999999", ['DisciplineName' => 'Does not matter']);
$patch->assertStatus(404);
}
public function testPartialUpdateDisciplineInvalidId()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/invalid", ['DisciplineName' => 'Bad']);
$patch->assertStatus(400);
}
public function testPartialUpdateDisciplineEmptyPayload()
{
$id = $this->createDiscipline();
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", []);
$patch->assertStatus(400);
}
public function testPartialUpdateDisciplineSingleField()
{
$id = $this->createDiscipline(['DisciplineName' => 'Original Name']);
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['DisciplineName' => 'New Name']);
$patch->assertStatus(200);
$discipline = $this->fetchDiscipline($id);
$this->assertEquals('New Name', $discipline['DisciplineName']);
}
}

View File

@ -1,35 +1,35 @@
<?php
namespace Tests\Feature\Organization;
use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
class HostAppControllerTest extends CIUnitTestCase
{
use FeatureTestTrait;
protected $endpoint = 'api/organization/hostapp';
protected function setUp(): void
{
parent::setUp();
}
public function testIndexHostApp()
{
$result = $this->get($this->endpoint);
$result->assertStatus(200);
}
public function testCreateHostApp()
{
$payload = [
'HostAppName' => 'Test Host Application',
'SiteID' => null
];
$result = $this->withBodyFormat('json')->post($this->endpoint, $payload);
$result->assertStatus(201);
}
}
<?php
namespace Tests\Feature\Organization;
use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
class HostAppControllerTest extends CIUnitTestCase
{
use FeatureTestTrait;
protected $endpoint = 'api/organization/hostapp';
protected function setUp(): void
{
parent::setUp();
}
public function testIndexHostApp()
{
$result = $this->get($this->endpoint);
$result->assertStatus(200);
}
public function testCreateHostApp()
{
$payload = [
'HostAppName' => 'Test Host Application',
'SiteID' => null
];
$result = $this->withBodyFormat('json')->post($this->endpoint, $payload);
$result->assertStatus(201);
}
}

View File

@ -1,122 +1,122 @@
<?php
namespace Tests\Feature\Organization;
use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
use Firebase\JWT\JWT;
class HostAppPatchTest extends CIUnitTestCase
{
use FeatureTestTrait;
protected string $token;
protected string $endpoint = 'api/organization/hostapp';
protected function setUp(): void
{
parent::setUp();
$key = getenv('JWT_SECRET') ?: 'my-secret-key';
$payload = [
'iss' => 'localhost',
'aud' => 'localhost',
'iat' => time(),
'nbf' => time(),
'exp' => time() + 3600,
'uid' => 1,
'email' => 'admin@admin.com',
];
$this->token = JWT::encode($payload, $key, 'HS256');
}
private function authHeaders(): array
{
return ['Cookie' => 'token=' . $this->token];
}
private function createHostApp(array $data = []): array
{
$payload = array_merge([
'HostAppName' => 'Test HostApp ' . uniqid(),
'SiteID' => null,
], $data);
$response = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('post', $this->endpoint, $payload);
$response->assertStatus(201);
$decoded = json_decode($response->getJSON(), true);
$id = $decoded['data'];
return array_merge(['HostAppID' => $id], $payload);
}
public function testPartialUpdateHostAppSuccess()
{
$app = $this->createHostApp();
$id = $app['HostAppID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['HostAppName' => 'Updated HostApp']);
$patch->assertStatus(200);
$patchData = json_decode($patch->getJSON(), true);
$this->assertEquals('success', $patchData['status']);
$show = $this->withHeaders($this->authHeaders())->call('get', "{$this->endpoint}/{$id}");
$show->assertStatus(200);
$showData = json_decode($show->getJSON(), true)['data'];
$this->assertEquals('Updated HostApp', $showData['HostAppName']);
$this->assertEquals($app['SiteID'], $showData['SiteID']);
}
public function testPartialUpdateHostAppNotFound()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/999999", ['HostAppName' => 'Updated']);
$patch->assertStatus(404);
}
public function testPartialUpdateHostAppInvalidId()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/invalid", ['HostAppName' => 'Updated']);
$patch->assertStatus(400);
}
public function testPartialUpdateHostAppEmptyPayload()
{
$app = $this->createHostApp();
$id = $app['HostAppID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", []);
$patch->assertStatus(400);
}
public function testPartialUpdateHostAppSingleField()
{
$app = $this->createHostApp();
$id = $app['HostAppID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['SiteID' => 5]);
$patch->assertStatus(200);
$showData = json_decode($this->withHeaders($this->authHeaders())
->call('get', "{$this->endpoint}/{$id}")
->getJSON(), true)['data'];
$this->assertNotEquals($app['SiteID'], $showData['SiteID']);
$this->assertEquals($app['HostAppName'], $showData['HostAppName']);
}
}
<?php
namespace Tests\Feature\Organization;
use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
use Firebase\JWT\JWT;
class HostAppPatchTest extends CIUnitTestCase
{
use FeatureTestTrait;
protected string $token;
protected string $endpoint = 'api/organization/hostapp';
protected function setUp(): void
{
parent::setUp();
$key = getenv('JWT_SECRET') ?: 'my-secret-key';
$payload = [
'iss' => 'localhost',
'aud' => 'localhost',
'iat' => time(),
'nbf' => time(),
'exp' => time() + 3600,
'uid' => 1,
'email' => 'admin@admin.com',
];
$this->token = JWT::encode($payload, $key, 'HS256');
}
private function authHeaders(): array
{
return ['Cookie' => 'token=' . $this->token];
}
private function createHostApp(array $data = []): array
{
$payload = array_merge([
'HostAppName' => 'Test HostApp ' . uniqid(),
'SiteID' => null,
], $data);
$response = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('post', $this->endpoint, $payload);
$response->assertStatus(201);
$decoded = json_decode($response->getJSON(), true);
$id = $decoded['data'];
return array_merge(['HostAppID' => $id], $payload);
}
public function testPartialUpdateHostAppSuccess()
{
$app = $this->createHostApp();
$id = $app['HostAppID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['HostAppName' => 'Updated HostApp']);
$patch->assertStatus(200);
$patchData = json_decode($patch->getJSON(), true);
$this->assertEquals('success', $patchData['status']);
$show = $this->withHeaders($this->authHeaders())->call('get', "{$this->endpoint}/{$id}");
$show->assertStatus(200);
$showData = json_decode($show->getJSON(), true)['data'];
$this->assertEquals('Updated HostApp', $showData['HostAppName']);
$this->assertEquals($app['SiteID'], $showData['SiteID']);
}
public function testPartialUpdateHostAppNotFound()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/999999", ['HostAppName' => 'Updated']);
$patch->assertStatus(404);
}
public function testPartialUpdateHostAppInvalidId()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/invalid", ['HostAppName' => 'Updated']);
$patch->assertStatus(400);
}
public function testPartialUpdateHostAppEmptyPayload()
{
$app = $this->createHostApp();
$id = $app['HostAppID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", []);
$patch->assertStatus(400);
}
public function testPartialUpdateHostAppSingleField()
{
$app = $this->createHostApp();
$id = $app['HostAppID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['SiteID' => 5]);
$patch->assertStatus(200);
$showData = json_decode($this->withHeaders($this->authHeaders())
->call('get', "{$this->endpoint}/{$id}")
->getJSON(), true)['data'];
$this->assertNotEquals($app['SiteID'], $showData['SiteID']);
$this->assertEquals($app['HostAppName'], $showData['HostAppName']);
}
}

View File

@ -1,121 +1,121 @@
<?php
namespace Tests\Feature\Organization;
use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
use Firebase\JWT\JWT;
class HostComParaPatchTest extends CIUnitTestCase
{
use FeatureTestTrait;
protected string $token;
protected string $endpoint = 'api/organization/hostcompara';
protected function setUp(): void
{
parent::setUp();
$key = getenv('JWT_SECRET') ?: 'my-secret-key';
$payload = [
'iss' => 'localhost',
'aud' => 'localhost',
'iat' => time(),
'nbf' => time(),
'exp' => time() + 3600,
'uid' => 1,
'email' => 'admin@admin.com',
];
$this->token = JWT::encode($payload, $key, 'HS256');
}
private function authHeaders(): array
{
return ['Cookie' => 'token=' . $this->token];
}
private function createHostComPara(array $data = []): array
{
$payload = array_merge([
'HostComParaCode' => 'HCP_' . uniqid(),
'HostComParaName' => 'Test HostComPara ' . uniqid(),
], $data);
$response = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('post', $this->endpoint, $payload);
$response->assertStatus(201);
$decoded = json_decode($response->getJSON(), true);
return $decoded['data'];
}
public function testPartialUpdateHostComParaSuccess()
{
$para = $this->createHostComPara();
$id = $para['HostComParaID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['HostComParaName' => 'Updated HostComPara']);
$patch->assertStatus(200);
$patchData = json_decode($patch->getJSON(), true);
$this->assertEquals('success', $patchData['status']);
$show = $this->withHeaders($this->authHeaders())->call('get', "{$this->endpoint}/{$id}");
$show->assertStatus(200);
$showData = json_decode($show->getJSON(), true)['data'];
$this->assertEquals('Updated HostComPara', $showData['HostComParaName']);
$this->assertEquals($para['HostComParaCode'], $showData['HostComParaCode']);
}
public function testPartialUpdateHostComParaNotFound()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/999999", ['HostComParaName' => 'Updated']);
$patch->assertStatus(404);
}
public function testPartialUpdateHostComParaInvalidId()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/invalid", ['HostComParaName' => 'Updated']);
$patch->assertStatus(400);
}
public function testPartialUpdateHostComParaEmptyPayload()
{
$para = $this->createHostComPara();
$id = $para['HostComParaID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", []);
$patch->assertStatus(400);
}
public function testPartialUpdateHostComParaSingleField()
{
$para = $this->createHostComPara();
$id = $para['HostComParaID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['HostComParaCode' => 'NEW_' . uniqid()]);
$patch->assertStatus(200);
$showData = json_decode($this->withHeaders($this->authHeaders())
->call('get', "{$this->endpoint}/{$id}")
->getJSON(), true)['data'];
$this->assertNotEquals($para['HostComParaCode'], $showData['HostComParaCode']);
$this->assertEquals($para['HostComParaName'], $showData['HostComParaName']);
}
}
<?php
namespace Tests\Feature\Organization;
use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
use Firebase\JWT\JWT;
class HostComParaPatchTest extends CIUnitTestCase
{
use FeatureTestTrait;
protected string $token;
protected string $endpoint = 'api/organization/hostcompara';
protected function setUp(): void
{
parent::setUp();
$key = getenv('JWT_SECRET') ?: 'my-secret-key';
$payload = [
'iss' => 'localhost',
'aud' => 'localhost',
'iat' => time(),
'nbf' => time(),
'exp' => time() + 3600,
'uid' => 1,
'email' => 'admin@admin.com',
];
$this->token = JWT::encode($payload, $key, 'HS256');
}
private function authHeaders(): array
{
return ['Cookie' => 'token=' . $this->token];
}
private function createHostComPara(array $data = []): array
{
$payload = array_merge([
'HostComParaCode' => 'HCP_' . uniqid(),
'HostComParaName' => 'Test HostComPara ' . uniqid(),
], $data);
$response = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('post', $this->endpoint, $payload);
$response->assertStatus(201);
$decoded = json_decode($response->getJSON(), true);
return $decoded['data'];
}
public function testPartialUpdateHostComParaSuccess()
{
$para = $this->createHostComPara();
$id = $para['HostComParaID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['HostComParaName' => 'Updated HostComPara']);
$patch->assertStatus(200);
$patchData = json_decode($patch->getJSON(), true);
$this->assertEquals('success', $patchData['status']);
$show = $this->withHeaders($this->authHeaders())->call('get', "{$this->endpoint}/{$id}");
$show->assertStatus(200);
$showData = json_decode($show->getJSON(), true)['data'];
$this->assertEquals('Updated HostComPara', $showData['HostComParaName']);
$this->assertEquals($para['HostComParaCode'], $showData['HostComParaCode']);
}
public function testPartialUpdateHostComParaNotFound()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/999999", ['HostComParaName' => 'Updated']);
$patch->assertStatus(404);
}
public function testPartialUpdateHostComParaInvalidId()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/invalid", ['HostComParaName' => 'Updated']);
$patch->assertStatus(400);
}
public function testPartialUpdateHostComParaEmptyPayload()
{
$para = $this->createHostComPara();
$id = $para['HostComParaID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", []);
$patch->assertStatus(400);
}
public function testPartialUpdateHostComParaSingleField()
{
$para = $this->createHostComPara();
$id = $para['HostComParaID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['HostComParaCode' => 'NEW_' . uniqid()]);
$patch->assertStatus(200);
$showData = json_decode($this->withHeaders($this->authHeaders())
->call('get', "{$this->endpoint}/{$id}")
->getJSON(), true)['data'];
$this->assertNotEquals($para['HostComParaCode'], $showData['HostComParaCode']);
$this->assertEquals($para['HostComParaName'], $showData['HostComParaName']);
}
}

View File

@ -1,121 +1,121 @@
<?php
namespace Tests\Feature\Organization;
use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
use Firebase\JWT\JWT;
class SitePatchTest extends CIUnitTestCase
{
use FeatureTestTrait;
protected string $token;
protected string $endpoint = 'api/organization/site';
protected function setUp(): void
{
parent::setUp();
$key = getenv('JWT_SECRET') ?: 'my-secret-key';
$payload = [
'iss' => 'localhost',
'aud' => 'localhost',
'iat' => time(),
'nbf' => time(),
'exp' => time() + 3600,
'uid' => 1,
'email' => 'admin@admin.com',
];
$this->token = JWT::encode($payload, $key, 'HS256');
}
private function authHeaders(): array
{
return ['Cookie' => 'token=' . $this->token];
}
private function createSite(array $data = []): array
{
$payload = array_merge([
'SiteCode' => 'SITE_' . uniqid(),
'SiteName' => 'Test Site ' . uniqid(),
], $data);
$response = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('post', $this->endpoint, $payload);
$response->assertStatus(201);
$decoded = json_decode($response->getJSON(), true);
return $decoded['data'];
}
public function testPartialUpdateSiteSuccess()
{
$site = $this->createSite();
$id = $site['SiteID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['SiteName' => 'Updated Site']);
$patch->assertStatus(200);
$patchData = json_decode($patch->getJSON(), true);
$this->assertEquals('success', $patchData['status']);
$show = $this->withHeaders($this->authHeaders())->call('get', "{$this->endpoint}/{$id}");
$show->assertStatus(200);
$showData = json_decode($show->getJSON(), true)['data'];
$this->assertEquals('Updated Site', $showData['SiteName']);
$this->assertEquals($site['SiteCode'], $showData['SiteCode']);
}
public function testPartialUpdateSiteNotFound()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/999999", ['SiteName' => 'Updated']);
$patch->assertStatus(404);
}
public function testPartialUpdateSiteInvalidId()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/invalid", ['SiteName' => 'Updated']);
$patch->assertStatus(400);
}
public function testPartialUpdateSiteEmptyPayload()
{
$site = $this->createSite();
$id = $site['SiteID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", []);
$patch->assertStatus(400);
}
public function testPartialUpdateSiteSingleField()
{
$site = $this->createSite();
$id = $site['SiteID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['SiteCode' => 'NEW_' . uniqid()]);
$patch->assertStatus(200);
$showData = json_decode($this->withHeaders($this->authHeaders())
->call('get', "{$this->endpoint}/{$id}")
->getJSON(), true)['data'];
$this->assertNotEquals($site['SiteCode'], $showData['SiteCode']);
$this->assertEquals($site['SiteName'], $showData['SiteName']);
}
}
<?php
namespace Tests\Feature\Organization;
use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
use Firebase\JWT\JWT;
class SitePatchTest extends CIUnitTestCase
{
use FeatureTestTrait;
protected string $token;
protected string $endpoint = 'api/organization/site';
protected function setUp(): void
{
parent::setUp();
$key = getenv('JWT_SECRET') ?: 'my-secret-key';
$payload = [
'iss' => 'localhost',
'aud' => 'localhost',
'iat' => time(),
'nbf' => time(),
'exp' => time() + 3600,
'uid' => 1,
'email' => 'admin@admin.com',
];
$this->token = JWT::encode($payload, $key, 'HS256');
}
private function authHeaders(): array
{
return ['Cookie' => 'token=' . $this->token];
}
private function createSite(array $data = []): array
{
$payload = array_merge([
'SiteCode' => 'SITE_' . uniqid(),
'SiteName' => 'Test Site ' . uniqid(),
], $data);
$response = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('post', $this->endpoint, $payload);
$response->assertStatus(201);
$decoded = json_decode($response->getJSON(), true);
return $decoded['data'];
}
public function testPartialUpdateSiteSuccess()
{
$site = $this->createSite();
$id = $site['SiteID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['SiteName' => 'Updated Site']);
$patch->assertStatus(200);
$patchData = json_decode($patch->getJSON(), true);
$this->assertEquals('success', $patchData['status']);
$show = $this->withHeaders($this->authHeaders())->call('get', "{$this->endpoint}/{$id}");
$show->assertStatus(200);
$showData = json_decode($show->getJSON(), true)['data'];
$this->assertEquals('Updated Site', $showData['SiteName']);
$this->assertEquals($site['SiteCode'], $showData['SiteCode']);
}
public function testPartialUpdateSiteNotFound()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/999999", ['SiteName' => 'Updated']);
$patch->assertStatus(404);
}
public function testPartialUpdateSiteInvalidId()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/invalid", ['SiteName' => 'Updated']);
$patch->assertStatus(400);
}
public function testPartialUpdateSiteEmptyPayload()
{
$site = $this->createSite();
$id = $site['SiteID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", []);
$patch->assertStatus(400);
}
public function testPartialUpdateSiteSingleField()
{
$site = $this->createSite();
$id = $site['SiteID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['SiteCode' => 'NEW_' . uniqid()]);
$patch->assertStatus(200);
$showData = json_decode($this->withHeaders($this->authHeaders())
->call('get', "{$this->endpoint}/{$id}")
->getJSON(), true)['data'];
$this->assertNotEquals($site['SiteCode'], $showData['SiteCode']);
$this->assertEquals($site['SiteName'], $showData['SiteName']);
}
}

View File

@ -1,122 +1,122 @@
<?php
namespace Tests\Feature\Organization;
use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
use Firebase\JWT\JWT;
class WorkstationPatchTest extends CIUnitTestCase
{
use FeatureTestTrait;
protected string $token;
protected string $endpoint = 'api/organization/workstation';
protected function setUp(): void
{
parent::setUp();
$key = getenv('JWT_SECRET') ?: 'my-secret-key';
$payload = [
'iss' => 'localhost',
'aud' => 'localhost',
'iat' => time(),
'nbf' => time(),
'exp' => time() + 3600,
'uid' => 1,
'email' => 'admin@admin.com',
];
$this->token = JWT::encode($payload, $key, 'HS256');
}
private function authHeaders(): array
{
return ['Cookie' => 'token=' . $this->token];
}
private function createWorkstation(array $data = []): array
{
$payload = array_merge([
'WorkstationCode' => 'WS_' . uniqid(),
'WorkstationName' => 'Test Workstation ' . uniqid(),
], $data);
$response = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('post', $this->endpoint, $payload);
$response->assertStatus(201);
$decoded = json_decode($response->getJSON(), true);
$id = $decoded['data'];
return array_merge(['WorkstationID' => $id], $payload);
}
public function testPartialUpdateWorkstationSuccess()
{
$ws = $this->createWorkstation();
$id = $ws['WorkstationID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['WorkstationName' => 'Updated Workstation']);
$patch->assertStatus(200);
$patchData = json_decode($patch->getJSON(), true);
$this->assertEquals('success', $patchData['status']);
$show = $this->withHeaders($this->authHeaders())->call('get', "{$this->endpoint}/{$id}");
$show->assertStatus(200);
$showData = json_decode($show->getJSON(), true)['data'];
$this->assertEquals('Updated Workstation', $showData['WorkstationName']);
$this->assertEquals($ws['WorkstationCode'], $showData['WorkstationCode']);
}
public function testPartialUpdateWorkstationNotFound()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/999999", ['WorkstationName' => 'Updated']);
$patch->assertStatus(404);
}
public function testPartialUpdateWorkstationInvalidId()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/invalid", ['WorkstationName' => 'Updated']);
$patch->assertStatus(400);
}
public function testPartialUpdateWorkstationEmptyPayload()
{
$ws = $this->createWorkstation();
$id = $ws['WorkstationID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", []);
$patch->assertStatus(400);
}
public function testPartialUpdateWorkstationSingleField()
{
$ws = $this->createWorkstation();
$id = $ws['WorkstationID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['WorkstationCode' => 'NEW_' . uniqid()]);
$patch->assertStatus(200);
$showData = json_decode($this->withHeaders($this->authHeaders())
->call('get', "{$this->endpoint}/{$id}")
->getJSON(), true)['data'];
$this->assertNotEquals($ws['WorkstationCode'], $showData['WorkstationCode']);
$this->assertEquals($ws['WorkstationName'], $showData['WorkstationName']);
}
}
<?php
namespace Tests\Feature\Organization;
use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
use Firebase\JWT\JWT;
class WorkstationPatchTest extends CIUnitTestCase
{
use FeatureTestTrait;
protected string $token;
protected string $endpoint = 'api/organization/workstation';
protected function setUp(): void
{
parent::setUp();
$key = getenv('JWT_SECRET') ?: 'my-secret-key';
$payload = [
'iss' => 'localhost',
'aud' => 'localhost',
'iat' => time(),
'nbf' => time(),
'exp' => time() + 3600,
'uid' => 1,
'email' => 'admin@admin.com',
];
$this->token = JWT::encode($payload, $key, 'HS256');
}
private function authHeaders(): array
{
return ['Cookie' => 'token=' . $this->token];
}
private function createWorkstation(array $data = []): array
{
$payload = array_merge([
'WorkstationCode' => 'WS_' . uniqid(),
'WorkstationName' => 'Test Workstation ' . uniqid(),
], $data);
$response = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('post', $this->endpoint, $payload);
$response->assertStatus(201);
$decoded = json_decode($response->getJSON(), true);
$id = $decoded['data'];
return array_merge(['WorkstationID' => $id], $payload);
}
public function testPartialUpdateWorkstationSuccess()
{
$ws = $this->createWorkstation();
$id = $ws['WorkstationID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['WorkstationName' => 'Updated Workstation']);
$patch->assertStatus(200);
$patchData = json_decode($patch->getJSON(), true);
$this->assertEquals('success', $patchData['status']);
$show = $this->withHeaders($this->authHeaders())->call('get', "{$this->endpoint}/{$id}");
$show->assertStatus(200);
$showData = json_decode($show->getJSON(), true)['data'];
$this->assertEquals('Updated Workstation', $showData['WorkstationName']);
$this->assertEquals($ws['WorkstationCode'], $showData['WorkstationCode']);
}
public function testPartialUpdateWorkstationNotFound()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/999999", ['WorkstationName' => 'Updated']);
$patch->assertStatus(404);
}
public function testPartialUpdateWorkstationInvalidId()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/invalid", ['WorkstationName' => 'Updated']);
$patch->assertStatus(400);
}
public function testPartialUpdateWorkstationEmptyPayload()
{
$ws = $this->createWorkstation();
$id = $ws['WorkstationID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", []);
$patch->assertStatus(400);
}
public function testPartialUpdateWorkstationSingleField()
{
$ws = $this->createWorkstation();
$id = $ws['WorkstationID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['WorkstationCode' => 'NEW_' . uniqid()]);
$patch->assertStatus(200);
$showData = json_decode($this->withHeaders($this->authHeaders())
->call('get', "{$this->endpoint}/{$id}")
->getJSON(), true)['data'];
$this->assertNotEquals($ws['WorkstationCode'], $showData['WorkstationCode']);
$this->assertEquals($ws['WorkstationName'], $showData['WorkstationName']);
}
}

View File

@ -1,97 +1,97 @@
<?php
namespace Tests\Feature\PatVisit;
use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
use Tests\Support\Traits\CreatesPatients;
class PatVisitADTPatchTest extends CIUnitTestCase
{
use FeatureTestTrait;
use CreatesPatients;
protected string $endpoint = 'api/patvisitadt';
protected function setUp(): void
{
parent::setUp();
}
private function createPatVisitADT(array $data = []): array
{
$payload = array_merge([
'InternalPID' => $this->createTestPatient(),
'ADTCode' => 'A01',
'LocationID' => '1',
], $data);
$response = $this->withBodyFormat('json')->call('post', $this->endpoint, $payload);
$response->assertStatus(201);
$decoded = json_decode($response->getJSON(), true);
return $decoded['data'];
}
public function testPartialUpdatePatVisitADTSuccess()
{
$adt = $this->createPatVisitADT();
$id = $adt['ADTID'];
$patch = $this->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['ADTCode' => 'A02']);
$patch->assertStatus(200);
$patchData = json_decode($patch->getJSON(), true);
$this->assertEquals('success', $patchData['status']);
$show = $this->call('get', "{$this->endpoint}/{$id}");
$show->assertStatus(200);
$showData = json_decode($show->getJSON(), true)['data'];
$this->assertEquals('A02', $showData['ADTCode']);
$this->assertEquals($adt['LocationID'], $showData['LocationID']);
}
public function testPartialUpdatePatVisitADTNotFound()
{
$patch = $this->withBodyFormat('json')
->call('patch', "{$this->endpoint}/999999", ['ADTCode' => 'A02']);
$patch->assertStatus(404);
}
public function testPartialUpdatePatVisitADTInvalidId()
{
$patch = $this->withBodyFormat('json')
->call('patch', "{$this->endpoint}/invalid", ['ADTCode' => 'A02']);
$patch->assertStatus(400);
}
public function testPartialUpdatePatVisitADTEmptyPayload()
{
$adt = $this->createPatVisitADT();
$id = $adt['ADTID'];
$patch = $this->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", []);
$patch->assertStatus(400);
}
public function testPartialUpdatePatVisitADTSingleField()
{
$adt = $this->createPatVisitADT();
$id = $adt['ADTID'];
$patch = $this->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['ADTCode' => 'A03']);
$patch->assertStatus(200);
$showData = json_decode($this->call('get', "{$this->endpoint}/{$id}")
->getJSON(), true)['data'];
$this->assertEquals('A03', $showData['ADTCode']);
$this->assertEquals($adt['LocationID'], $showData['LocationID']);
}
}
<?php
namespace Tests\Feature\PatVisit;
use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
use Tests\Support\Traits\CreatesPatients;
class PatVisitADTPatchTest extends CIUnitTestCase
{
use FeatureTestTrait;
use CreatesPatients;
protected string $endpoint = 'api/patvisitadt';
protected function setUp(): void
{
parent::setUp();
}
private function createPatVisitADT(array $data = []): array
{
$payload = array_merge([
'InternalPID' => $this->createTestPatient(),
'ADTCode' => 'A01',
'LocationID' => '1',
], $data);
$response = $this->withBodyFormat('json')->call('post', $this->endpoint, $payload);
$response->assertStatus(201);
$decoded = json_decode($response->getJSON(), true);
return $decoded['data'];
}
public function testPartialUpdatePatVisitADTSuccess()
{
$adt = $this->createPatVisitADT();
$id = $adt['ADTID'];
$patch = $this->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['ADTCode' => 'A02']);
$patch->assertStatus(200);
$patchData = json_decode($patch->getJSON(), true);
$this->assertEquals('success', $patchData['status']);
$show = $this->call('get', "{$this->endpoint}/{$id}");
$show->assertStatus(200);
$showData = json_decode($show->getJSON(), true)['data'];
$this->assertEquals('A02', $showData['ADTCode']);
$this->assertEquals($adt['LocationID'], $showData['LocationID']);
}
public function testPartialUpdatePatVisitADTNotFound()
{
$patch = $this->withBodyFormat('json')
->call('patch', "{$this->endpoint}/999999", ['ADTCode' => 'A02']);
$patch->assertStatus(404);
}
public function testPartialUpdatePatVisitADTInvalidId()
{
$patch = $this->withBodyFormat('json')
->call('patch', "{$this->endpoint}/invalid", ['ADTCode' => 'A02']);
$patch->assertStatus(400);
}
public function testPartialUpdatePatVisitADTEmptyPayload()
{
$adt = $this->createPatVisitADT();
$id = $adt['ADTID'];
$patch = $this->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", []);
$patch->assertStatus(400);
}
public function testPartialUpdatePatVisitADTSingleField()
{
$adt = $this->createPatVisitADT();
$id = $adt['ADTID'];
$patch = $this->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['ADTCode' => 'A03']);
$patch->assertStatus(200);
$showData = json_decode($this->call('get', "{$this->endpoint}/{$id}")
->getJSON(), true)['data'];
$this->assertEquals('A03', $showData['ADTCode']);
$this->assertEquals($adt['LocationID'], $showData['LocationID']);
}
}

View File

@ -1,119 +1,119 @@
<?php
namespace Tests\Feature\Patients;
use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\FeatureTestTrait;
use Faker\Factory;
class PatientCheckTest extends CIUnitTestCase
{
use FeatureTestTrait;
protected $endpoint = 'api/patient/check';
public function testCheckPatientIDExists()
{
$result = $this->withHeaders(['Accept' => 'application/json'])->call('get', $this->endpoint, [
'PatientID' => 'SMAJ1',
]);
$result->assertStatus(200);
$data = json_decode($result->getJSON(), true);
$this->assertSame('success', $data['status']);
$this->assertSame(false, $data['data']);
}
public function testCheckPatientIDWithHyphenExists()
{
$result = $this->withHeaders(['Accept' => 'application/json'])->call('get', $this->endpoint, [
'PatientID' => 'SMAJ-1',
]);
$result->assertStatus(200);
$data = json_decode($result->getJSON(), true);
$this->assertSame('success', $data['status']);
$this->assertIsBool($data['data']);
}
public function testCheckPatientIDNotExists()
{
$faker = Factory::create();
$result = $this->withHeaders(['Accept' => 'application/json'])->call('get', $this->endpoint, [
'PatientID' => 'NONEXISTENT-' . $faker->numberBetween(100000, 999999),
]);
$result->assertStatus(200);
$data = json_decode($result->getJSON(), true);
$this->assertSame('success', $data['status']);
$this->assertTrue($data['data']);
}
public function testCheckEmailAddressExists()
{
$result = $this->withHeaders(['Accept' => 'application/json'])->call('get', $this->endpoint, [
'EmailAddress' => 'dummy1@test.com',
]);
$result->assertStatus(200);
$data = json_decode($result->getJSON(), true);
$this->assertSame('success', $data['status']);
$this->assertIsBool($data['data']);
}
public function testCheckEmailAddressMatchesSecondaryColumn()
{
$result = $this->withHeaders(['Accept' => 'application/json'])->call('get', $this->endpoint, [
'EmailAddress' => 'dummy1@test.com',
]);
$result->assertStatus(200);
$data = json_decode($result->getJSON(), true);
$this->assertSame('success', $data['status']);
$this->assertIsBool($data['data']);
}
public function testCheckPhoneExists()
{
$result = $this->withHeaders(['Accept' => 'application/json'])->call('get', $this->endpoint, [
'Phone' => '092029',
]);
$result->assertStatus(200);
$data = json_decode($result->getJSON(), true);
$this->assertSame('success', $data['status']);
$this->assertIsBool($data['data']);
}
public function testCheckWithoutParams()
{
$result = $this->withHeaders(['Accept' => 'application/json'])->call('get', $this->endpoint);
$result->assertStatus(400);
$data = json_decode($result->getJSON(), true);
$this->assertSame('error', $data['status']);
$this->assertSame('PatientID, EmailAddress, or Phone parameter is required.', $data['message']);
}
public function testCheckResponseStructure()
{
$result = $this->withHeaders(['Accept' => 'application/json'])->call('get', $this->endpoint, [
'PatientID' => 'TEST123',
]);
$result->assertStatus(200);
$data = json_decode($result->getJSON(), true);
$this->assertArrayHasKey('status', $data);
$this->assertArrayHasKey('message', $data);
$this->assertArrayHasKey('data', $data);
}
}
<?php
namespace Tests\Feature\Patients;
use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\FeatureTestTrait;
use Faker\Factory;
class PatientCheckTest extends CIUnitTestCase
{
use FeatureTestTrait;
protected $endpoint = 'api/patient/check';
public function testCheckPatientIDExists()
{
$result = $this->withHeaders(['Accept' => 'application/json'])->call('get', $this->endpoint, [
'PatientID' => 'SMAJ1',
]);
$result->assertStatus(200);
$data = json_decode($result->getJSON(), true);
$this->assertSame('success', $data['status']);
$this->assertSame(false, $data['data']);
}
public function testCheckPatientIDWithHyphenExists()
{
$result = $this->withHeaders(['Accept' => 'application/json'])->call('get', $this->endpoint, [
'PatientID' => 'SMAJ-1',
]);
$result->assertStatus(200);
$data = json_decode($result->getJSON(), true);
$this->assertSame('success', $data['status']);
$this->assertIsBool($data['data']);
}
public function testCheckPatientIDNotExists()
{
$faker = Factory::create();
$result = $this->withHeaders(['Accept' => 'application/json'])->call('get', $this->endpoint, [
'PatientID' => 'NONEXISTENT-' . $faker->numberBetween(100000, 999999),
]);
$result->assertStatus(200);
$data = json_decode($result->getJSON(), true);
$this->assertSame('success', $data['status']);
$this->assertTrue($data['data']);
}
public function testCheckEmailAddressExists()
{
$result = $this->withHeaders(['Accept' => 'application/json'])->call('get', $this->endpoint, [
'EmailAddress' => 'dummy1@test.com',
]);
$result->assertStatus(200);
$data = json_decode($result->getJSON(), true);
$this->assertSame('success', $data['status']);
$this->assertIsBool($data['data']);
}
public function testCheckEmailAddressMatchesSecondaryColumn()
{
$result = $this->withHeaders(['Accept' => 'application/json'])->call('get', $this->endpoint, [
'EmailAddress' => 'dummy1@test.com',
]);
$result->assertStatus(200);
$data = json_decode($result->getJSON(), true);
$this->assertSame('success', $data['status']);
$this->assertIsBool($data['data']);
}
public function testCheckPhoneExists()
{
$result = $this->withHeaders(['Accept' => 'application/json'])->call('get', $this->endpoint, [
'Phone' => '092029',
]);
$result->assertStatus(200);
$data = json_decode($result->getJSON(), true);
$this->assertSame('success', $data['status']);
$this->assertIsBool($data['data']);
}
public function testCheckWithoutParams()
{
$result = $this->withHeaders(['Accept' => 'application/json'])->call('get', $this->endpoint);
$result->assertStatus(400);
$data = json_decode($result->getJSON(), true);
$this->assertSame('error', $data['status']);
$this->assertSame('PatientID, EmailAddress, or Phone parameter is required.', $data['message']);
}
public function testCheckResponseStructure()
{
$result = $this->withHeaders(['Accept' => 'application/json'])->call('get', $this->endpoint, [
'PatientID' => 'TEST123',
]);
$result->assertStatus(200);
$data = json_decode($result->getJSON(), true);
$this->assertArrayHasKey('status', $data);
$this->assertArrayHasKey('message', $data);
$this->assertArrayHasKey('data', $data);
}
}

View File

@ -1,121 +1,121 @@
<?php
namespace Tests\Feature\Result;
use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
use Firebase\JWT\JWT;
class ResultPatchTest extends CIUnitTestCase
{
use FeatureTestTrait;
protected string $token;
protected string $endpoint = 'api/result';
protected function setUp(): void
{
parent::setUp();
$key = getenv('JWT_SECRET') ?: 'my-secret-key';
$payload = [
'iss' => 'localhost',
'aud' => 'localhost',
'iat' => time(),
'nbf' => time(),
'exp' => time() + 3600,
'uid' => 1,
'email' => 'admin@admin.com',
];
$this->token = JWT::encode($payload, $key, 'HS256');
}
private function authHeaders(): array
{
return ['Cookie' => 'token=' . $this->token];
}
private function createResult(array $data = []): array
{
$payload = array_merge([
'ResultCode' => 'RES_' . uniqid(),
'ResultValue' => 'Test Value ' . uniqid(),
], $data);
$response = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('post', $this->endpoint, $payload);
$response->assertStatus(201);
$decoded = json_decode($response->getJSON(), true);
return $decoded['data'];
}
public function testPartialUpdateResultSuccess()
{
$result = $this->createResult();
$id = $result['ResultID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['ResultValue' => 'Updated Value']);
$patch->assertStatus(200);
$patchData = json_decode($patch->getJSON(), true);
$this->assertEquals('success', $patchData['status']);
$show = $this->withHeaders($this->authHeaders())->call('get', "{$this->endpoint}/{$id}");
$show->assertStatus(200);
$showData = json_decode($show->getJSON(), true)['data'];
$this->assertEquals('Updated Value', $showData['ResultValue']);
$this->assertEquals($result['ResultCode'], $showData['ResultCode']);
}
public function testPartialUpdateResultNotFound()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/999999", ['ResultValue' => 'Updated']);
$patch->assertStatus(404);
}
public function testPartialUpdateResultInvalidId()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/invalid", ['ResultValue' => 'Updated']);
$patch->assertStatus(400);
}
public function testPartialUpdateResultEmptyPayload()
{
$result = $this->createResult();
$id = $result['ResultID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", []);
$patch->assertStatus(400);
}
public function testPartialUpdateResultSingleField()
{
$result = $this->createResult();
$id = $result['ResultID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['ResultCode' => 'NEW_' . uniqid()]);
$patch->assertStatus(200);
$showData = json_decode($this->withHeaders($this->authHeaders())
->call('get', "{$this->endpoint}/{$id}")
->getJSON(), true)['data'];
$this->assertNotEquals($result['ResultCode'], $showData['ResultCode']);
$this->assertEquals($result['ResultValue'], $showData['ResultValue']);
}
}
<?php
namespace Tests\Feature\Result;
use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
use Firebase\JWT\JWT;
class ResultPatchTest extends CIUnitTestCase
{
use FeatureTestTrait;
protected string $token;
protected string $endpoint = 'api/result';
protected function setUp(): void
{
parent::setUp();
$key = getenv('JWT_SECRET') ?: 'my-secret-key';
$payload = [
'iss' => 'localhost',
'aud' => 'localhost',
'iat' => time(),
'nbf' => time(),
'exp' => time() + 3600,
'uid' => 1,
'email' => 'admin@admin.com',
];
$this->token = JWT::encode($payload, $key, 'HS256');
}
private function authHeaders(): array
{
return ['Cookie' => 'token=' . $this->token];
}
private function createResult(array $data = []): array
{
$payload = array_merge([
'ResultCode' => 'RES_' . uniqid(),
'ResultValue' => 'Test Value ' . uniqid(),
], $data);
$response = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('post', $this->endpoint, $payload);
$response->assertStatus(201);
$decoded = json_decode($response->getJSON(), true);
return $decoded['data'];
}
public function testPartialUpdateResultSuccess()
{
$result = $this->createResult();
$id = $result['ResultID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['ResultValue' => 'Updated Value']);
$patch->assertStatus(200);
$patchData = json_decode($patch->getJSON(), true);
$this->assertEquals('success', $patchData['status']);
$show = $this->withHeaders($this->authHeaders())->call('get', "{$this->endpoint}/{$id}");
$show->assertStatus(200);
$showData = json_decode($show->getJSON(), true)['data'];
$this->assertEquals('Updated Value', $showData['ResultValue']);
$this->assertEquals($result['ResultCode'], $showData['ResultCode']);
}
public function testPartialUpdateResultNotFound()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/999999", ['ResultValue' => 'Updated']);
$patch->assertStatus(404);
}
public function testPartialUpdateResultInvalidId()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/invalid", ['ResultValue' => 'Updated']);
$patch->assertStatus(400);
}
public function testPartialUpdateResultEmptyPayload()
{
$result = $this->createResult();
$id = $result['ResultID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", []);
$patch->assertStatus(400);
}
public function testPartialUpdateResultSingleField()
{
$result = $this->createResult();
$id = $result['ResultID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['ResultCode' => 'NEW_' . uniqid()]);
$patch->assertStatus(200);
$showData = json_decode($this->withHeaders($this->authHeaders())
->call('get', "{$this->endpoint}/{$id}")
->getJSON(), true)['data'];
$this->assertNotEquals($result['ResultCode'], $showData['ResultCode']);
$this->assertEquals($result['ResultValue'], $showData['ResultValue']);
}
}

View File

@ -1,122 +1,122 @@
<?php
namespace Tests\Feature\Rule;
use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
use Firebase\JWT\JWT;
class RulePatchTest extends CIUnitTestCase
{
use FeatureTestTrait;
protected string $token;
protected string $endpoint = 'api/rule';
protected function setUp(): void
{
parent::setUp();
$key = getenv('JWT_SECRET') ?: 'my-secret-key';
$payload = [
'iss' => 'localhost',
'aud' => 'localhost',
'iat' => time(),
'nbf' => time(),
'exp' => time() + 3600,
'uid' => 1,
'email' => 'admin@admin.com',
];
$this->token = JWT::encode($payload, $key, 'HS256');
}
private function authHeaders(): array
{
return ['Cookie' => 'token=' . $this->token];
}
private function createRule(array $data = []): array
{
$payload = array_merge([
'RuleCode' => 'RULE_' . uniqid(),
'RuleName' => 'Test Rule ' . uniqid(),
'RuleExpression' => 'test_expression',
], $data);
$response = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('post', $this->endpoint, $payload);
$response->assertStatus(201);
$decoded = json_decode($response->getJSON(), true);
return $decoded['data'];
}
public function testPartialUpdateRuleSuccess()
{
$rule = $this->createRule();
$id = $rule['RuleID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['RuleName' => 'Updated Rule']);
$patch->assertStatus(200);
$patchData = json_decode($patch->getJSON(), true);
$this->assertEquals('success', $patchData['status']);
$show = $this->withHeaders($this->authHeaders())->call('get', "{$this->endpoint}/{$id}");
$show->assertStatus(200);
$showData = json_decode($show->getJSON(), true)['data'];
$this->assertEquals('Updated Rule', $showData['RuleName']);
$this->assertEquals($rule['RuleCode'], $showData['RuleCode']);
}
public function testPartialUpdateRuleNotFound()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/999999", ['RuleName' => 'Updated']);
$patch->assertStatus(404);
}
public function testPartialUpdateRuleInvalidId()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/invalid", ['RuleName' => 'Updated']);
$patch->assertStatus(400);
}
public function testPartialUpdateRuleEmptyPayload()
{
$rule = $this->createRule();
$id = $rule['RuleID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", []);
$patch->assertStatus(400);
}
public function testPartialUpdateRuleSingleField()
{
$rule = $this->createRule();
$id = $rule['RuleID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['RuleCode' => 'NEW_' . uniqid()]);
$patch->assertStatus(200);
$showData = json_decode($this->withHeaders($this->authHeaders())
->call('get', "{$this->endpoint}/{$id}")
->getJSON(), true)['data'];
$this->assertNotEquals($rule['RuleCode'], $showData['RuleCode']);
$this->assertEquals($rule['RuleName'], $showData['RuleName']);
}
}
<?php
namespace Tests\Feature\Rule;
use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
use Firebase\JWT\JWT;
class RulePatchTest extends CIUnitTestCase
{
use FeatureTestTrait;
protected string $token;
protected string $endpoint = 'api/rule';
protected function setUp(): void
{
parent::setUp();
$key = getenv('JWT_SECRET') ?: 'my-secret-key';
$payload = [
'iss' => 'localhost',
'aud' => 'localhost',
'iat' => time(),
'nbf' => time(),
'exp' => time() + 3600,
'uid' => 1,
'email' => 'admin@admin.com',
];
$this->token = JWT::encode($payload, $key, 'HS256');
}
private function authHeaders(): array
{
return ['Cookie' => 'token=' . $this->token];
}
private function createRule(array $data = []): array
{
$payload = array_merge([
'RuleCode' => 'RULE_' . uniqid(),
'RuleName' => 'Test Rule ' . uniqid(),
'RuleExpression' => 'test_expression',
], $data);
$response = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('post', $this->endpoint, $payload);
$response->assertStatus(201);
$decoded = json_decode($response->getJSON(), true);
return $decoded['data'];
}
public function testPartialUpdateRuleSuccess()
{
$rule = $this->createRule();
$id = $rule['RuleID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['RuleName' => 'Updated Rule']);
$patch->assertStatus(200);
$patchData = json_decode($patch->getJSON(), true);
$this->assertEquals('success', $patchData['status']);
$show = $this->withHeaders($this->authHeaders())->call('get', "{$this->endpoint}/{$id}");
$show->assertStatus(200);
$showData = json_decode($show->getJSON(), true)['data'];
$this->assertEquals('Updated Rule', $showData['RuleName']);
$this->assertEquals($rule['RuleCode'], $showData['RuleCode']);
}
public function testPartialUpdateRuleNotFound()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/999999", ['RuleName' => 'Updated']);
$patch->assertStatus(404);
}
public function testPartialUpdateRuleInvalidId()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/invalid", ['RuleName' => 'Updated']);
$patch->assertStatus(400);
}
public function testPartialUpdateRuleEmptyPayload()
{
$rule = $this->createRule();
$id = $rule['RuleID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", []);
$patch->assertStatus(400);
}
public function testPartialUpdateRuleSingleField()
{
$rule = $this->createRule();
$id = $rule['RuleID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['RuleCode' => 'NEW_' . uniqid()]);
$patch->assertStatus(200);
$showData = json_decode($this->withHeaders($this->authHeaders())
->call('get', "{$this->endpoint}/{$id}")
->getJSON(), true)['data'];
$this->assertNotEquals($rule['RuleCode'], $showData['RuleCode']);
$this->assertEquals($rule['RuleName'], $showData['RuleName']);
}
}

View File

@ -1,118 +1,118 @@
<?php
namespace Tests\Feature\Specimen;
use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
use Firebase\JWT\JWT;
class CollectionPatchTest extends CIUnitTestCase
{
use FeatureTestTrait;
protected string $token;
protected string $endpoint = 'api/specimen/collection';
protected function setUp(): void
{
parent::setUp();
$key = getenv('JWT_SECRET') ?: 'my-secret-key';
$payload = [
'iss' => 'localhost',
'aud' => 'localhost',
'iat' => time(),
'nbf' => time(),
'exp' => time() + 3600,
'uid' => 1,
'email' => 'admin@admin.com',
];
$this->token = JWT::encode($payload, $key, 'HS256');
}
private function authHeaders(): array
{
return ['Cookie' => 'token=' . $this->token];
}
private function createCollection(array $data = []): array
{
$payload = array_merge([
'BodySite' => 'BodySite_' . uniqid(),
], $data);
$response = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('post', $this->endpoint, $payload);
$response->assertStatus(201);
$decoded = json_decode($response->getJSON(), true);
return $decoded['data'];
}
public function testPartialUpdateCollectionSuccess()
{
$collection = $this->createCollection();
$id = $collection['SpcColID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['BodySite' => 'UpdatedBodySite']);
$patch->assertStatus(200);
$patchData = json_decode($patch->getJSON(), true);
$this->assertEquals('success', $patchData['status']);
$show = $this->withHeaders($this->authHeaders())->call('get', "{$this->endpoint}/{$id}");
$show->assertStatus(200);
$showData = json_decode($show->getJSON(), true)['data'];
$this->assertEquals('UpdatedBodySite', $showData['BodySite']);
}
public function testPartialUpdateCollectionNotFound()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/999999", ['BodySite' => 'Updated']);
$patch->assertStatus(404);
}
public function testPartialUpdateCollectionInvalidId()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/invalid", ['BodySite' => 'Updated']);
$patch->assertStatus(400);
}
public function testPartialUpdateCollectionEmptyPayload()
{
$collection = $this->createCollection();
$id = $collection['SpcColID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", []);
$patch->assertStatus(400);
}
public function testPartialUpdateCollectionSingleField()
{
$collection = $this->createCollection();
$id = $collection['SpcColID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['BodySite' => 'NewBodySite']);
$patch->assertStatus(200);
$showData = json_decode($this->withHeaders($this->authHeaders())
->call('get', "{$this->endpoint}/{$id}")
->getJSON(), true)['data'];
$this->assertEquals('NewBodySite', $showData['BodySite']);
}
}
<?php
namespace Tests\Feature\Specimen;
use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
use Firebase\JWT\JWT;
class CollectionPatchTest extends CIUnitTestCase
{
use FeatureTestTrait;
protected string $token;
protected string $endpoint = 'api/specimen/collection';
protected function setUp(): void
{
parent::setUp();
$key = getenv('JWT_SECRET') ?: 'my-secret-key';
$payload = [
'iss' => 'localhost',
'aud' => 'localhost',
'iat' => time(),
'nbf' => time(),
'exp' => time() + 3600,
'uid' => 1,
'email' => 'admin@admin.com',
];
$this->token = JWT::encode($payload, $key, 'HS256');
}
private function authHeaders(): array
{
return ['Cookie' => 'token=' . $this->token];
}
private function createCollection(array $data = []): array
{
$payload = array_merge([
'BodySite' => 'BodySite_' . uniqid(),
], $data);
$response = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('post', $this->endpoint, $payload);
$response->assertStatus(201);
$decoded = json_decode($response->getJSON(), true);
return $decoded['data'];
}
public function testPartialUpdateCollectionSuccess()
{
$collection = $this->createCollection();
$id = $collection['SpcColID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['BodySite' => 'UpdatedBodySite']);
$patch->assertStatus(200);
$patchData = json_decode($patch->getJSON(), true);
$this->assertEquals('success', $patchData['status']);
$show = $this->withHeaders($this->authHeaders())->call('get', "{$this->endpoint}/{$id}");
$show->assertStatus(200);
$showData = json_decode($show->getJSON(), true)['data'];
$this->assertEquals('UpdatedBodySite', $showData['BodySite']);
}
public function testPartialUpdateCollectionNotFound()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/999999", ['BodySite' => 'Updated']);
$patch->assertStatus(404);
}
public function testPartialUpdateCollectionInvalidId()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/invalid", ['BodySite' => 'Updated']);
$patch->assertStatus(400);
}
public function testPartialUpdateCollectionEmptyPayload()
{
$collection = $this->createCollection();
$id = $collection['SpcColID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", []);
$patch->assertStatus(400);
}
public function testPartialUpdateCollectionSingleField()
{
$collection = $this->createCollection();
$id = $collection['SpcColID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['BodySite' => 'NewBodySite']);
$patch->assertStatus(200);
$showData = json_decode($this->withHeaders($this->authHeaders())
->call('get', "{$this->endpoint}/{$id}")
->getJSON(), true)['data'];
$this->assertEquals('NewBodySite', $showData['BodySite']);
}
}

View File

@ -1,122 +1,122 @@
<?php
namespace Tests\Feature\Specimen;
use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
use Firebase\JWT\JWT;
class ContainerPatchTest extends CIUnitTestCase
{
use FeatureTestTrait;
protected string $token;
protected string $endpoint = 'api/specimen/container';
protected function setUp(): void
{
parent::setUp();
$key = getenv('JWT_SECRET') ?: 'my-secret-key';
$payload = [
'iss' => 'localhost',
'aud' => 'localhost',
'iat' => time(),
'nbf' => time(),
'exp' => time() + 3600,
'uid' => 1,
'email' => 'admin@admin.com',
];
$this->token = JWT::encode($payload, $key, 'HS256');
}
private function authHeaders(): array
{
return ['Cookie' => 'token=' . $this->token];
}
private function createContainer(array $data = []): array
{
$payload = array_merge([
'ConCode' => 'CON_' . uniqid(),
'ConName' => 'Test Container ' . uniqid(),
], $data);
$response = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('post', $this->endpoint, $payload);
$response->assertStatus(201);
$decoded = json_decode($response->getJSON(), true);
$id = $decoded['data'];
return array_merge(['ConDefID' => $id], $payload);
}
public function testPartialUpdateContainerSuccess()
{
$container = $this->createContainer();
$id = $container['ConDefID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['ConName' => 'Updated Container']);
$patch->assertStatus(200);
$patchData = json_decode($patch->getJSON(), true);
$this->assertEquals('success', $patchData['status']);
$show = $this->withHeaders($this->authHeaders())->call('get', "{$this->endpoint}/{$id}");
$show->assertStatus(200);
$showData = json_decode($show->getJSON(), true)['data'];
$this->assertEquals('Updated Container', $showData['ConName']);
$this->assertEquals($container['ConCode'], $showData['ConCode']);
}
public function testPartialUpdateContainerNotFound()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/999999", ['ConName' => 'Updated']);
$patch->assertStatus(404);
}
public function testPartialUpdateContainerInvalidId()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/invalid", ['ConName' => 'Updated']);
$patch->assertStatus(400);
}
public function testPartialUpdateContainerEmptyPayload()
{
$container = $this->createContainer();
$id = $container['ConDefID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", []);
$patch->assertStatus(400);
}
public function testPartialUpdateContainerSingleField()
{
$container = $this->createContainer();
$id = $container['ConDefID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['ConCode' => 'NEW_' . uniqid()]);
$patch->assertStatus(200);
$showData = json_decode($this->withHeaders($this->authHeaders())
->call('get', "{$this->endpoint}/{$id}")
->getJSON(), true)['data'];
$this->assertNotEquals($container['ConCode'], $showData['ConCode']);
$this->assertEquals($container['ConName'], $showData['ConName']);
}
}
<?php
namespace Tests\Feature\Specimen;
use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
use Firebase\JWT\JWT;
class ContainerPatchTest extends CIUnitTestCase
{
use FeatureTestTrait;
protected string $token;
protected string $endpoint = 'api/specimen/container';
protected function setUp(): void
{
parent::setUp();
$key = getenv('JWT_SECRET') ?: 'my-secret-key';
$payload = [
'iss' => 'localhost',
'aud' => 'localhost',
'iat' => time(),
'nbf' => time(),
'exp' => time() + 3600,
'uid' => 1,
'email' => 'admin@admin.com',
];
$this->token = JWT::encode($payload, $key, 'HS256');
}
private function authHeaders(): array
{
return ['Cookie' => 'token=' . $this->token];
}
private function createContainer(array $data = []): array
{
$payload = array_merge([
'ConCode' => 'CON_' . uniqid(),
'ConName' => 'Test Container ' . uniqid(),
], $data);
$response = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('post', $this->endpoint, $payload);
$response->assertStatus(201);
$decoded = json_decode($response->getJSON(), true);
$id = $decoded['data'];
return array_merge(['ConDefID' => $id], $payload);
}
public function testPartialUpdateContainerSuccess()
{
$container = $this->createContainer();
$id = $container['ConDefID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['ConName' => 'Updated Container']);
$patch->assertStatus(200);
$patchData = json_decode($patch->getJSON(), true);
$this->assertEquals('success', $patchData['status']);
$show = $this->withHeaders($this->authHeaders())->call('get', "{$this->endpoint}/{$id}");
$show->assertStatus(200);
$showData = json_decode($show->getJSON(), true)['data'];
$this->assertEquals('Updated Container', $showData['ConName']);
$this->assertEquals($container['ConCode'], $showData['ConCode']);
}
public function testPartialUpdateContainerNotFound()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/999999", ['ConName' => 'Updated']);
$patch->assertStatus(404);
}
public function testPartialUpdateContainerInvalidId()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/invalid", ['ConName' => 'Updated']);
$patch->assertStatus(400);
}
public function testPartialUpdateContainerEmptyPayload()
{
$container = $this->createContainer();
$id = $container['ConDefID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", []);
$patch->assertStatus(400);
}
public function testPartialUpdateContainerSingleField()
{
$container = $this->createContainer();
$id = $container['ConDefID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['ConCode' => 'NEW_' . uniqid()]);
$patch->assertStatus(200);
$showData = json_decode($this->withHeaders($this->authHeaders())
->call('get', "{$this->endpoint}/{$id}")
->getJSON(), true)['data'];
$this->assertNotEquals($container['ConCode'], $showData['ConCode']);
$this->assertEquals($container['ConName'], $showData['ConName']);
}
}

View File

@ -1,121 +1,121 @@
<?php
namespace Tests\Feature\Specimen;
use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
use Firebase\JWT\JWT;
class PrepPatchTest extends CIUnitTestCase
{
use FeatureTestTrait;
protected string $token;
protected string $endpoint = 'api/specimen/prep';
protected function setUp(): void
{
parent::setUp();
$key = getenv('JWT_SECRET') ?: 'my-secret-key';
$payload = [
'iss' => 'localhost',
'aud' => 'localhost',
'iat' => time(),
'nbf' => time(),
'exp' => time() + 3600,
'uid' => 1,
'email' => 'admin@admin.com',
];
$this->token = JWT::encode($payload, $key, 'HS256');
}
private function authHeaders(): array
{
return ['Cookie' => 'token=' . $this->token];
}
private function createPrep(array $data = []): array
{
$payload = array_merge([
'SpcPrpCode' => 'PREP_' . uniqid(),
'Description' => 'Test Prep ' . uniqid(),
], $data);
$response = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('post', $this->endpoint, $payload);
$response->assertStatus(201);
$decoded = json_decode($response->getJSON(), true);
return $decoded['data'];
}
public function testPartialUpdatePrepSuccess()
{
$prep = $this->createPrep();
$id = $prep['SpcPrpID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['Description' => 'Updated Prep']);
$patch->assertStatus(200);
$patchData = json_decode($patch->getJSON(), true);
$this->assertEquals('success', $patchData['status']);
$show = $this->withHeaders($this->authHeaders())->call('get', "{$this->endpoint}/{$id}");
$show->assertStatus(200);
$showData = json_decode($show->getJSON(), true)['data'];
$this->assertEquals('Updated Prep', $showData['Description']);
$this->assertEquals($prep['SpcPrpCode'], $showData['SpcPrpCode']);
}
public function testPartialUpdatePrepNotFound()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/999999", ['Description' => 'Updated']);
$patch->assertStatus(404);
}
public function testPartialUpdatePrepInvalidId()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/invalid", ['Description' => 'Updated']);
$patch->assertStatus(400);
}
public function testPartialUpdatePrepEmptyPayload()
{
$prep = $this->createPrep();
$id = $prep['SpcPrpID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", []);
$patch->assertStatus(400);
}
public function testPartialUpdatePrepSingleField()
{
$prep = $this->createPrep();
$id = $prep['SpcPrpID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['SpcPrpCode' => 'NEW_' . uniqid()]);
$patch->assertStatus(200);
$showData = json_decode($this->withHeaders($this->authHeaders())
->call('get', "{$this->endpoint}/{$id}")
->getJSON(), true)['data'];
$this->assertNotEquals($prep['SpcPrpCode'], $showData['SpcPrpCode']);
$this->assertEquals($prep['Description'], $showData['Description']);
}
}
<?php
namespace Tests\Feature\Specimen;
use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
use Firebase\JWT\JWT;
class PrepPatchTest extends CIUnitTestCase
{
use FeatureTestTrait;
protected string $token;
protected string $endpoint = 'api/specimen/prep';
protected function setUp(): void
{
parent::setUp();
$key = getenv('JWT_SECRET') ?: 'my-secret-key';
$payload = [
'iss' => 'localhost',
'aud' => 'localhost',
'iat' => time(),
'nbf' => time(),
'exp' => time() + 3600,
'uid' => 1,
'email' => 'admin@admin.com',
];
$this->token = JWT::encode($payload, $key, 'HS256');
}
private function authHeaders(): array
{
return ['Cookie' => 'token=' . $this->token];
}
private function createPrep(array $data = []): array
{
$payload = array_merge([
'SpcPrpCode' => 'PREP_' . uniqid(),
'Description' => 'Test Prep ' . uniqid(),
], $data);
$response = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('post', $this->endpoint, $payload);
$response->assertStatus(201);
$decoded = json_decode($response->getJSON(), true);
return $decoded['data'];
}
public function testPartialUpdatePrepSuccess()
{
$prep = $this->createPrep();
$id = $prep['SpcPrpID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['Description' => 'Updated Prep']);
$patch->assertStatus(200);
$patchData = json_decode($patch->getJSON(), true);
$this->assertEquals('success', $patchData['status']);
$show = $this->withHeaders($this->authHeaders())->call('get', "{$this->endpoint}/{$id}");
$show->assertStatus(200);
$showData = json_decode($show->getJSON(), true)['data'];
$this->assertEquals('Updated Prep', $showData['Description']);
$this->assertEquals($prep['SpcPrpCode'], $showData['SpcPrpCode']);
}
public function testPartialUpdatePrepNotFound()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/999999", ['Description' => 'Updated']);
$patch->assertStatus(404);
}
public function testPartialUpdatePrepInvalidId()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/invalid", ['Description' => 'Updated']);
$patch->assertStatus(400);
}
public function testPartialUpdatePrepEmptyPayload()
{
$prep = $this->createPrep();
$id = $prep['SpcPrpID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", []);
$patch->assertStatus(400);
}
public function testPartialUpdatePrepSingleField()
{
$prep = $this->createPrep();
$id = $prep['SpcPrpID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['SpcPrpCode' => 'NEW_' . uniqid()]);
$patch->assertStatus(200);
$showData = json_decode($this->withHeaders($this->authHeaders())
->call('get', "{$this->endpoint}/{$id}")
->getJSON(), true)['data'];
$this->assertNotEquals($prep['SpcPrpCode'], $showData['SpcPrpCode']);
$this->assertEquals($prep['Description'], $showData['Description']);
}
}

View File

@ -1,121 +1,121 @@
<?php
namespace Tests\Feature\Specimen;
use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
use Firebase\JWT\JWT;
class SpecimenPatchTest extends CIUnitTestCase
{
use FeatureTestTrait;
protected string $token;
protected string $endpoint = 'api/specimen';
protected function setUp(): void
{
parent::setUp();
$key = getenv('JWT_SECRET') ?: 'my-secret-key';
$payload = [
'iss' => 'localhost',
'aud' => 'localhost',
'iat' => time(),
'nbf' => time(),
'exp' => time() + 3600,
'uid' => 1,
'email' => 'admin@admin.com',
];
$this->token = JWT::encode($payload, $key, 'HS256');
}
private function authHeaders(): array
{
return ['Cookie' => 'token=' . $this->token];
}
private function createSpecimen(array $data = []): array
{
$payload = array_merge([
'SpecimenCode' => 'SPEC_' . uniqid(),
'SpecimenName' => 'Test Specimen ' . uniqid(),
], $data);
$response = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('post', $this->endpoint, $payload);
$response->assertStatus(201);
$decoded = json_decode($response->getJSON(), true);
return $decoded['data'];
}
public function testPartialUpdateSpecimenSuccess()
{
$specimen = $this->createSpecimen();
$id = $specimen['SpecimenID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['SpecimenName' => 'Updated Specimen']);
$patch->assertStatus(200);
$patchData = json_decode($patch->getJSON(), true);
$this->assertEquals('success', $patchData['status']);
$show = $this->withHeaders($this->authHeaders())->call('get', "{$this->endpoint}/{$id}");
$show->assertStatus(200);
$showData = json_decode($show->getJSON(), true)['data'];
$this->assertEquals('Updated Specimen', $showData['SpecimenName']);
$this->assertEquals($specimen['SpecimenCode'], $showData['SpecimenCode']);
}
public function testPartialUpdateSpecimenNotFound()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/999999", ['SpecimenName' => 'Updated']);
$patch->assertStatus(404);
}
public function testPartialUpdateSpecimenInvalidId()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/invalid", ['SpecimenName' => 'Updated']);
$patch->assertStatus(400);
}
public function testPartialUpdateSpecimenEmptyPayload()
{
$specimen = $this->createSpecimen();
$id = $specimen['SpecimenID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", []);
$patch->assertStatus(400);
}
public function testPartialUpdateSpecimenSingleField()
{
$specimen = $this->createSpecimen();
$id = $specimen['SpecimenID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['SpecimenCode' => 'NEW_' . uniqid()]);
$patch->assertStatus(200);
$showData = json_decode($this->withHeaders($this->authHeaders())
->call('get', "{$this->endpoint}/{$id}")
->getJSON(), true)['data'];
$this->assertNotEquals($specimen['SpecimenCode'], $showData['SpecimenCode']);
$this->assertEquals($specimen['SpecimenName'], $showData['SpecimenName']);
}
}
<?php
namespace Tests\Feature\Specimen;
use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
use Firebase\JWT\JWT;
class SpecimenPatchTest extends CIUnitTestCase
{
use FeatureTestTrait;
protected string $token;
protected string $endpoint = 'api/specimen';
protected function setUp(): void
{
parent::setUp();
$key = getenv('JWT_SECRET') ?: 'my-secret-key';
$payload = [
'iss' => 'localhost',
'aud' => 'localhost',
'iat' => time(),
'nbf' => time(),
'exp' => time() + 3600,
'uid' => 1,
'email' => 'admin@admin.com',
];
$this->token = JWT::encode($payload, $key, 'HS256');
}
private function authHeaders(): array
{
return ['Cookie' => 'token=' . $this->token];
}
private function createSpecimen(array $data = []): array
{
$payload = array_merge([
'SpecimenCode' => 'SPEC_' . uniqid(),
'SpecimenName' => 'Test Specimen ' . uniqid(),
], $data);
$response = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('post', $this->endpoint, $payload);
$response->assertStatus(201);
$decoded = json_decode($response->getJSON(), true);
return $decoded['data'];
}
public function testPartialUpdateSpecimenSuccess()
{
$specimen = $this->createSpecimen();
$id = $specimen['SpecimenID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['SpecimenName' => 'Updated Specimen']);
$patch->assertStatus(200);
$patchData = json_decode($patch->getJSON(), true);
$this->assertEquals('success', $patchData['status']);
$show = $this->withHeaders($this->authHeaders())->call('get', "{$this->endpoint}/{$id}");
$show->assertStatus(200);
$showData = json_decode($show->getJSON(), true)['data'];
$this->assertEquals('Updated Specimen', $showData['SpecimenName']);
$this->assertEquals($specimen['SpecimenCode'], $showData['SpecimenCode']);
}
public function testPartialUpdateSpecimenNotFound()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/999999", ['SpecimenName' => 'Updated']);
$patch->assertStatus(404);
}
public function testPartialUpdateSpecimenInvalidId()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/invalid", ['SpecimenName' => 'Updated']);
$patch->assertStatus(400);
}
public function testPartialUpdateSpecimenEmptyPayload()
{
$specimen = $this->createSpecimen();
$id = $specimen['SpecimenID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", []);
$patch->assertStatus(400);
}
public function testPartialUpdateSpecimenSingleField()
{
$specimen = $this->createSpecimen();
$id = $specimen['SpecimenID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['SpecimenCode' => 'NEW_' . uniqid()]);
$patch->assertStatus(200);
$showData = json_decode($this->withHeaders($this->authHeaders())
->call('get', "{$this->endpoint}/{$id}")
->getJSON(), true)['data'];
$this->assertNotEquals($specimen['SpecimenCode'], $showData['SpecimenCode']);
$this->assertEquals($specimen['SpecimenName'], $showData['SpecimenName']);
}
}

View File

@ -1,118 +1,118 @@
<?php
namespace Tests\Feature\Specimen;
use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
use Firebase\JWT\JWT;
class StatusPatchTest extends CIUnitTestCase
{
use FeatureTestTrait;
protected string $token;
protected string $endpoint = 'api/specimen/status';
protected function setUp(): void
{
parent::setUp();
$key = getenv('JWT_SECRET') ?: 'my-secret-key';
$payload = [
'iss' => 'localhost',
'aud' => 'localhost',
'iat' => time(),
'nbf' => time(),
'exp' => time() + 3600,
'uid' => 1,
'email' => 'admin@admin.com',
];
$this->token = JWT::encode($payload, $key, 'HS256');
}
private function authHeaders(): array
{
return ['Cookie' => 'token=' . $this->token];
}
private function createStatus(array $data = []): array
{
$payload = array_merge([
'SpcStatus' => 'Status_' . uniqid(),
], $data);
$response = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('post', $this->endpoint, $payload);
$response->assertStatus(201);
$decoded = json_decode($response->getJSON(), true);
return $decoded['data'];
}
public function testPartialUpdateStatusSuccess()
{
$status = $this->createStatus();
$id = $status['SpcStaID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['SpcStatus' => 'UpdatedStatus']);
$patch->assertStatus(200);
$patchData = json_decode($patch->getJSON(), true);
$this->assertEquals('success', $patchData['status']);
$show = $this->withHeaders($this->authHeaders())->call('get', "{$this->endpoint}/{$id}");
$show->assertStatus(200);
$showData = json_decode($show->getJSON(), true)['data'];
$this->assertEquals('UpdatedStatus', $showData['SpcStatus']);
}
public function testPartialUpdateStatusNotFound()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/999999", ['SpcStatus' => 'Updated']);
$patch->assertStatus(404);
}
public function testPartialUpdateStatusInvalidId()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/invalid", ['SpcStatus' => 'Updated']);
$patch->assertStatus(400);
}
public function testPartialUpdateStatusEmptyPayload()
{
$status = $this->createStatus();
$id = $status['SpcStaID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", []);
$patch->assertStatus(400);
}
public function testPartialUpdateStatusSingleField()
{
$status = $this->createStatus();
$id = $status['SpcStaID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['SpcStatus' => 'NewStatus']);
$patch->assertStatus(200);
$showData = json_decode($this->withHeaders($this->authHeaders())
->call('get', "{$this->endpoint}/{$id}")
->getJSON(), true)['data'];
$this->assertEquals('NewStatus', $showData['SpcStatus']);
}
}
<?php
namespace Tests\Feature\Specimen;
use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
use Firebase\JWT\JWT;
class StatusPatchTest extends CIUnitTestCase
{
use FeatureTestTrait;
protected string $token;
protected string $endpoint = 'api/specimen/status';
protected function setUp(): void
{
parent::setUp();
$key = getenv('JWT_SECRET') ?: 'my-secret-key';
$payload = [
'iss' => 'localhost',
'aud' => 'localhost',
'iat' => time(),
'nbf' => time(),
'exp' => time() + 3600,
'uid' => 1,
'email' => 'admin@admin.com',
];
$this->token = JWT::encode($payload, $key, 'HS256');
}
private function authHeaders(): array
{
return ['Cookie' => 'token=' . $this->token];
}
private function createStatus(array $data = []): array
{
$payload = array_merge([
'SpcStatus' => 'Status_' . uniqid(),
], $data);
$response = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('post', $this->endpoint, $payload);
$response->assertStatus(201);
$decoded = json_decode($response->getJSON(), true);
return $decoded['data'];
}
public function testPartialUpdateStatusSuccess()
{
$status = $this->createStatus();
$id = $status['SpcStaID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['SpcStatus' => 'UpdatedStatus']);
$patch->assertStatus(200);
$patchData = json_decode($patch->getJSON(), true);
$this->assertEquals('success', $patchData['status']);
$show = $this->withHeaders($this->authHeaders())->call('get', "{$this->endpoint}/{$id}");
$show->assertStatus(200);
$showData = json_decode($show->getJSON(), true)['data'];
$this->assertEquals('UpdatedStatus', $showData['SpcStatus']);
}
public function testPartialUpdateStatusNotFound()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/999999", ['SpcStatus' => 'Updated']);
$patch->assertStatus(404);
}
public function testPartialUpdateStatusInvalidId()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/invalid", ['SpcStatus' => 'Updated']);
$patch->assertStatus(400);
}
public function testPartialUpdateStatusEmptyPayload()
{
$status = $this->createStatus();
$id = $status['SpcStaID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", []);
$patch->assertStatus(400);
}
public function testPartialUpdateStatusSingleField()
{
$status = $this->createStatus();
$id = $status['SpcStaID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['SpcStatus' => 'NewStatus']);
$patch->assertStatus(200);
$showData = json_decode($this->withHeaders($this->authHeaders())
->call('get', "{$this->endpoint}/{$id}")
->getJSON(), true)['data'];
$this->assertEquals('NewStatus', $showData['SpcStatus']);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,140 +1,140 @@
<?php
namespace Tests\Feature\Test;
use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
use Firebase\JWT\JWT;
class TestMapDetailPatchTest extends CIUnitTestCase
{
use FeatureTestTrait;
protected string $token;
protected string $endpoint = 'api/test/testmap/detail';
protected string $mapEndpoint = 'api/test/testmap';
protected function setUp(): void
{
parent::setUp();
$key = getenv('JWT_SECRET') ?: 'my-secret-key';
$payload = [
'iss' => 'localhost',
'aud' => 'localhost',
'iat' => time(),
'nbf' => time(),
'exp' => time() + 3600,
'uid' => 1,
'email' => 'admin@admin.com',
];
$this->token = JWT::encode($payload, $key, 'HS256');
}
private function authHeaders(): array
{
return ['Cookie' => 'token=' . $this->token];
}
private function createTestMapDetail(array $data = []): array
{
$mapResponse = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('post', $this->mapEndpoint, [
'HostType' => 'SITE',
'HostID' => 1,
'ClientType' => 'SITE',
'ClientID' => 1,
]);
$mapResponse->assertStatus(201);
$mapID = json_decode($mapResponse->getJSON(), true)['data'];
$payload = array_merge([
'TestMapID' => $mapID,
'HostTestCode' => 'HB',
'HostTestName' => 'Hemoglobin',
'ClientTestCode' => '2',
'ClientTestName' => 'Hemoglobin',
], $data);
$response = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('post', $this->endpoint, $payload);
$response->assertStatus(201);
$decoded = json_decode($response->getJSON(), true);
$detailID = $decoded['data'];
$show = $this->withHeaders($this->authHeaders())->call('get', "{$this->endpoint}/{$detailID}");
$show->assertStatus(200);
return json_decode($show->getJSON(), true)['data'];
}
public function testPartialUpdateTestMapDetailSuccess()
{
$detail = $this->createTestMapDetail();
$id = $detail['TestMapDetailID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['ClientTestName' => 'Updated Detail']);
$patch->assertStatus(200);
$patchData = json_decode($patch->getJSON(), true);
$this->assertEquals('success', $patchData['status']);
$show = $this->withHeaders($this->authHeaders())->call('get', "{$this->endpoint}/{$id}");
$show->assertStatus(200);
$showData = json_decode($show->getJSON(), true)['data'];
$this->assertEquals('Updated Detail', $showData['ClientTestName']);
$this->assertEquals($detail['HostTestCode'], $showData['HostTestCode']);
}
public function testPartialUpdateTestMapDetailNotFound()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/999999", ['ClientTestName' => 'Updated']);
$patch->assertStatus(404);
}
public function testPartialUpdateTestMapDetailInvalidId()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/invalid", ['ClientTestName' => 'Updated']);
$patch->assertStatus(400);
}
public function testPartialUpdateTestMapDetailEmptyPayload()
{
$detail = $this->createTestMapDetail();
$id = $detail['TestMapDetailID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", []);
$patch->assertStatus(400);
}
public function testPartialUpdateTestMapDetailSingleField()
{
$detail = $this->createTestMapDetail();
$id = $detail['TestMapDetailID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['HostTestCode' => 'HBA1C']);
$patch->assertStatus(200);
$showData = json_decode($this->withHeaders($this->authHeaders())
->call('get', "{$this->endpoint}/{$id}")
->getJSON(), true)['data'];
$this->assertNotEquals($detail['HostTestCode'], $showData['HostTestCode']);
$this->assertEquals($detail['ClientTestName'], $showData['ClientTestName']);
}
}
<?php
namespace Tests\Feature\Test;
use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
use Firebase\JWT\JWT;
class TestMapDetailPatchTest extends CIUnitTestCase
{
use FeatureTestTrait;
protected string $token;
protected string $endpoint = 'api/test/testmap/detail';
protected string $mapEndpoint = 'api/test/testmap';
protected function setUp(): void
{
parent::setUp();
$key = getenv('JWT_SECRET') ?: 'my-secret-key';
$payload = [
'iss' => 'localhost',
'aud' => 'localhost',
'iat' => time(),
'nbf' => time(),
'exp' => time() + 3600,
'uid' => 1,
'email' => 'admin@admin.com',
];
$this->token = JWT::encode($payload, $key, 'HS256');
}
private function authHeaders(): array
{
return ['Cookie' => 'token=' . $this->token];
}
private function createTestMapDetail(array $data = []): array
{
$mapResponse = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('post', $this->mapEndpoint, [
'HostType' => 'SITE',
'HostID' => 1,
'ClientType' => 'SITE',
'ClientID' => 1,
]);
$mapResponse->assertStatus(201);
$mapID = json_decode($mapResponse->getJSON(), true)['data'];
$payload = array_merge([
'TestMapID' => $mapID,
'HostTestCode' => 'HB',
'HostTestName' => 'Hemoglobin',
'ClientTestCode' => '2',
'ClientTestName' => 'Hemoglobin',
], $data);
$response = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('post', $this->endpoint, $payload);
$response->assertStatus(201);
$decoded = json_decode($response->getJSON(), true);
$detailID = $decoded['data'];
$show = $this->withHeaders($this->authHeaders())->call('get', "{$this->endpoint}/{$detailID}");
$show->assertStatus(200);
return json_decode($show->getJSON(), true)['data'];
}
public function testPartialUpdateTestMapDetailSuccess()
{
$detail = $this->createTestMapDetail();
$id = $detail['TestMapDetailID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['ClientTestName' => 'Updated Detail']);
$patch->assertStatus(200);
$patchData = json_decode($patch->getJSON(), true);
$this->assertEquals('success', $patchData['status']);
$show = $this->withHeaders($this->authHeaders())->call('get', "{$this->endpoint}/{$id}");
$show->assertStatus(200);
$showData = json_decode($show->getJSON(), true)['data'];
$this->assertEquals('Updated Detail', $showData['ClientTestName']);
$this->assertEquals($detail['HostTestCode'], $showData['HostTestCode']);
}
public function testPartialUpdateTestMapDetailNotFound()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/999999", ['ClientTestName' => 'Updated']);
$patch->assertStatus(404);
}
public function testPartialUpdateTestMapDetailInvalidId()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/invalid", ['ClientTestName' => 'Updated']);
$patch->assertStatus(400);
}
public function testPartialUpdateTestMapDetailEmptyPayload()
{
$detail = $this->createTestMapDetail();
$id = $detail['TestMapDetailID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", []);
$patch->assertStatus(400);
}
public function testPartialUpdateTestMapDetailSingleField()
{
$detail = $this->createTestMapDetail();
$id = $detail['TestMapDetailID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['HostTestCode' => 'HBA1C']);
$patch->assertStatus(200);
$showData = json_decode($this->withHeaders($this->authHeaders())
->call('get', "{$this->endpoint}/{$id}")
->getJSON(), true)['data'];
$this->assertNotEquals($detail['HostTestCode'], $showData['HostTestCode']);
$this->assertEquals($detail['ClientTestName'], $showData['ClientTestName']);
}
}

View File

@ -1,38 +1,38 @@
<?php
declare(strict_types=1);
namespace Tests\Feature\Test;
use App\Models\Test\TestDefSiteModel;
use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\FeatureTestTrait;
class TestShowResponseTest extends CIUnitTestCase
{
use FeatureTestTrait;
public function testShowTechnicalDoesNotReturnNestedTestDefTech(): void
{
$model = new TestDefSiteModel();
$test = $model->where('TestSiteCode', 'GLU')->where('EndDate IS NULL')->first();
if (!$test) {
$test = $model->where('TestType', 'TEST')->where('EndDate IS NULL')->first();
}
$this->assertNotEmpty($test, 'No active technical test record found for show endpoint test.');
$response = $this->call('get', 'api/test/' . $test['TestSiteID']);
$response->assertStatus(200);
$json = json_decode($response->getJSON(), true);
$this->assertSame('success', $json['status'] ?? null);
$this->assertArrayHasKey('data', $json);
$this->assertArrayNotHasKey('testdeftech', $json['data']);
$this->assertArrayHasKey('TestSiteID', $json['data']);
$this->assertArrayHasKey('ResultType', $json['data']);
}
}
<?php
declare(strict_types=1);
namespace Tests\Feature\Test;
use App\Models\Test\TestDefSiteModel;
use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\FeatureTestTrait;
class TestShowResponseTest extends CIUnitTestCase
{
use FeatureTestTrait;
public function testShowTechnicalDoesNotReturnNestedTestDefTech(): void
{
$model = new TestDefSiteModel();
$test = $model->where('TestSiteCode', 'GLU')->where('EndDate IS NULL')->first();
if (!$test) {
$test = $model->where('TestType', 'TEST')->where('EndDate IS NULL')->first();
}
$this->assertNotEmpty($test, 'No active technical test record found for show endpoint test.');
$response = $this->call('get', 'api/test/' . $test['TestSiteID']);
$response->assertStatus(200);
$json = json_decode($response->getJSON(), true);
$this->assertSame('success', $json['status'] ?? null);
$this->assertArrayHasKey('data', $json);
$this->assertArrayNotHasKey('testdeftech', $json['data']);
$this->assertArrayHasKey('TestSiteID', $json['data']);
$this->assertArrayHasKey('ResultType', $json['data']);
}
}

View File

@ -1,121 +1,121 @@
<?php
namespace Tests\Feature\Test;
use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
use Firebase\JWT\JWT;
class TestsPatchTest extends CIUnitTestCase
{
use FeatureTestTrait;
protected string $token;
protected string $endpoint = 'api/test';
protected function setUp(): void
{
parent::setUp();
$key = getenv('JWT_SECRET') ?: 'my-secret-key';
$payload = [
'iss' => 'localhost',
'aud' => 'localhost',
'iat' => time(),
'nbf' => time(),
'exp' => time() + 3600,
'uid' => 1,
'email' => 'admin@admin.com',
];
$this->token = JWT::encode($payload, $key, 'HS256');
}
private function authHeaders(): array
{
return ['Cookie' => 'token=' . $this->token];
}
private function createTest(array $data = []): array
{
$payload = array_merge([
'TestCode' => 'TEST_' . uniqid(),
'TestName' => 'Test Name ' . uniqid(),
], $data);
$response = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('post', $this->endpoint, $payload);
$response->assertStatus(201);
$decoded = json_decode($response->getJSON(), true);
return $decoded['data'];
}
public function testPartialUpdateTestSuccess()
{
$test = $this->createTest();
$id = $test['TestID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['TestName' => 'Updated Test']);
$patch->assertStatus(200);
$patchData = json_decode($patch->getJSON(), true);
$this->assertEquals('success', $patchData['status']);
$show = $this->withHeaders($this->authHeaders())->call('get', "{$this->endpoint}/{$id}");
$show->assertStatus(200);
$showData = json_decode($show->getJSON(), true)['data'];
$this->assertEquals('Updated Test', $showData['TestName']);
$this->assertEquals($test['TestCode'], $showData['TestCode']);
}
public function testPartialUpdateTestNotFound()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/999999", ['TestName' => 'Updated']);
$patch->assertStatus(404);
}
public function testPartialUpdateTestInvalidId()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/invalid", ['TestName' => 'Updated']);
$patch->assertStatus(400);
}
public function testPartialUpdateTestEmptyPayload()
{
$test = $this->createTest();
$id = $test['TestID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", []);
$patch->assertStatus(400);
}
public function testPartialUpdateTestSingleField()
{
$test = $this->createTest();
$id = $test['TestID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['TestCode' => 'NEW_' . uniqid()]);
$patch->assertStatus(200);
$showData = json_decode($this->withHeaders($this->authHeaders())
->call('get', "{$this->endpoint}/{$id}")
->getJSON(), true)['data'];
$this->assertNotEquals($test['TestCode'], $showData['TestCode']);
$this->assertEquals($test['TestName'], $showData['TestName']);
}
}
<?php
namespace Tests\Feature\Test;
use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
use Firebase\JWT\JWT;
class TestsPatchTest extends CIUnitTestCase
{
use FeatureTestTrait;
protected string $token;
protected string $endpoint = 'api/test';
protected function setUp(): void
{
parent::setUp();
$key = getenv('JWT_SECRET') ?: 'my-secret-key';
$payload = [
'iss' => 'localhost',
'aud' => 'localhost',
'iat' => time(),
'nbf' => time(),
'exp' => time() + 3600,
'uid' => 1,
'email' => 'admin@admin.com',
];
$this->token = JWT::encode($payload, $key, 'HS256');
}
private function authHeaders(): array
{
return ['Cookie' => 'token=' . $this->token];
}
private function createTest(array $data = []): array
{
$payload = array_merge([
'TestCode' => 'TEST_' . uniqid(),
'TestName' => 'Test Name ' . uniqid(),
], $data);
$response = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('post', $this->endpoint, $payload);
$response->assertStatus(201);
$decoded = json_decode($response->getJSON(), true);
return $decoded['data'];
}
public function testPartialUpdateTestSuccess()
{
$test = $this->createTest();
$id = $test['TestID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['TestName' => 'Updated Test']);
$patch->assertStatus(200);
$patchData = json_decode($patch->getJSON(), true);
$this->assertEquals('success', $patchData['status']);
$show = $this->withHeaders($this->authHeaders())->call('get', "{$this->endpoint}/{$id}");
$show->assertStatus(200);
$showData = json_decode($show->getJSON(), true)['data'];
$this->assertEquals('Updated Test', $showData['TestName']);
$this->assertEquals($test['TestCode'], $showData['TestCode']);
}
public function testPartialUpdateTestNotFound()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/999999", ['TestName' => 'Updated']);
$patch->assertStatus(404);
}
public function testPartialUpdateTestInvalidId()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/invalid", ['TestName' => 'Updated']);
$patch->assertStatus(400);
}
public function testPartialUpdateTestEmptyPayload()
{
$test = $this->createTest();
$id = $test['TestID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", []);
$patch->assertStatus(400);
}
public function testPartialUpdateTestSingleField()
{
$test = $this->createTest();
$id = $test['TestID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['TestCode' => 'NEW_' . uniqid()]);
$patch->assertStatus(200);
$showData = json_decode($this->withHeaders($this->authHeaders())
->call('get', "{$this->endpoint}/{$id}")
->getJSON(), true)['data'];
$this->assertNotEquals($test['TestCode'], $showData['TestCode']);
$this->assertEquals($test['TestName'], $showData['TestName']);
}
}

View File

@ -1,122 +1,122 @@
<?php
namespace Tests\Feature\User;
use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
use Firebase\JWT\JWT;
class UserPatchTest extends CIUnitTestCase
{
use FeatureTestTrait;
protected string $token;
protected string $endpoint = 'api/user';
protected function setUp(): void
{
parent::setUp();
$key = getenv('JWT_SECRET') ?: 'my-secret-key';
$payload = [
'iss' => 'localhost',
'aud' => 'localhost',
'iat' => time(),
'nbf' => time(),
'exp' => time() + 3600,
'uid' => 1,
'email' => 'admin@admin.com',
];
$this->token = JWT::encode($payload, $key, 'HS256');
}
private function authHeaders(): array
{
return ['Cookie' => 'token=' . $this->token];
}
private function createUser(array $data = []): array
{
$payload = array_merge([
'UserCode' => 'USR_' . uniqid(),
'UserName' => 'Test User ' . uniqid(),
'Email' => 'user_' . uniqid() . '@test.com',
], $data);
$response = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('post', $this->endpoint, $payload);
$response->assertStatus(201);
$decoded = json_decode($response->getJSON(), true);
return $decoded['data'];
}
public function testPartialUpdateUserSuccess()
{
$user = $this->createUser();
$id = $user['UserID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['UserName' => 'Updated User']);
$patch->assertStatus(200);
$patchData = json_decode($patch->getJSON(), true);
$this->assertEquals('success', $patchData['status']);
$show = $this->withHeaders($this->authHeaders())->call('get', "{$this->endpoint}/{$id}");
$show->assertStatus(200);
$showData = json_decode($show->getJSON(), true)['data'];
$this->assertEquals('Updated User', $showData['UserName']);
$this->assertEquals($user['UserCode'], $showData['UserCode']);
}
public function testPartialUpdateUserNotFound()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/999999", ['UserName' => 'Updated']);
$patch->assertStatus(404);
}
public function testPartialUpdateUserInvalidId()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/invalid", ['UserName' => 'Updated']);
$patch->assertStatus(400);
}
public function testPartialUpdateUserEmptyPayload()
{
$user = $this->createUser();
$id = $user['UserID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", []);
$patch->assertStatus(400);
}
public function testPartialUpdateUserSingleField()
{
$user = $this->createUser();
$id = $user['UserID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['UserCode' => 'NEW_' . uniqid()]);
$patch->assertStatus(200);
$showData = json_decode($this->withHeaders($this->authHeaders())
->call('get', "{$this->endpoint}/{$id}")
->getJSON(), true)['data'];
$this->assertNotEquals($user['UserCode'], $showData['UserCode']);
$this->assertEquals($user['UserName'], $showData['UserName']);
}
}
<?php
namespace Tests\Feature\User;
use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
use Firebase\JWT\JWT;
class UserPatchTest extends CIUnitTestCase
{
use FeatureTestTrait;
protected string $token;
protected string $endpoint = 'api/user';
protected function setUp(): void
{
parent::setUp();
$key = getenv('JWT_SECRET') ?: 'my-secret-key';
$payload = [
'iss' => 'localhost',
'aud' => 'localhost',
'iat' => time(),
'nbf' => time(),
'exp' => time() + 3600,
'uid' => 1,
'email' => 'admin@admin.com',
];
$this->token = JWT::encode($payload, $key, 'HS256');
}
private function authHeaders(): array
{
return ['Cookie' => 'token=' . $this->token];
}
private function createUser(array $data = []): array
{
$payload = array_merge([
'UserCode' => 'USR_' . uniqid(),
'UserName' => 'Test User ' . uniqid(),
'Email' => 'user_' . uniqid() . '@test.com',
], $data);
$response = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('post', $this->endpoint, $payload);
$response->assertStatus(201);
$decoded = json_decode($response->getJSON(), true);
return $decoded['data'];
}
public function testPartialUpdateUserSuccess()
{
$user = $this->createUser();
$id = $user['UserID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['UserName' => 'Updated User']);
$patch->assertStatus(200);
$patchData = json_decode($patch->getJSON(), true);
$this->assertEquals('success', $patchData['status']);
$show = $this->withHeaders($this->authHeaders())->call('get', "{$this->endpoint}/{$id}");
$show->assertStatus(200);
$showData = json_decode($show->getJSON(), true)['data'];
$this->assertEquals('Updated User', $showData['UserName']);
$this->assertEquals($user['UserCode'], $showData['UserCode']);
}
public function testPartialUpdateUserNotFound()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/999999", ['UserName' => 'Updated']);
$patch->assertStatus(404);
}
public function testPartialUpdateUserInvalidId()
{
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/invalid", ['UserName' => 'Updated']);
$patch->assertStatus(400);
}
public function testPartialUpdateUserEmptyPayload()
{
$user = $this->createUser();
$id = $user['UserID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", []);
$patch->assertStatus(400);
}
public function testPartialUpdateUserSingleField()
{
$user = $this->createUser();
$id = $user['UserID'];
$patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['UserCode' => 'NEW_' . uniqid()]);
$patch->assertStatus(200);
$showData = json_decode($this->withHeaders($this->authHeaders())
->call('get', "{$this->endpoint}/{$id}")
->getJSON(), true)['data'];
$this->assertNotEquals($user['UserCode'], $showData['UserCode']);
$this->assertEquals($user['UserName'], $showData['UserName']);
}
}

View File

@ -1,50 +1,50 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/codeigniter4/framework/system/Test/bootstrap.php';
use CodeIgniter\Config\DotEnv;
use CodeIgniter\Database\Exceptions\DatabaseException;
use CodeIgniter\Database\MigrationRunner;
use Config\Database;
use Config\Migrations as MigrationsConfig;
if (defined('CLQMS_PHPUNIT_BOOTSTRAPPED') || ENVIRONMENT !== 'testing') {
return;
}
define('CLQMS_PHPUNIT_BOOTSTRAPPED', true);
(new DotEnv(ROOTPATH))->load();
$db = Database::connect('tests');
$forge = Database::forge('tests');
$db->query('SET FOREIGN_KEY_CHECKS=0');
foreach ($db->listTables() as $table) {
$forge->dropTable($table, true);
}
$db->query('SET FOREIGN_KEY_CHECKS=1');
$migrationsConfig = config(MigrationsConfig::class);
$migrationRunner = new MigrationRunner($migrationsConfig, 'tests');
try {
$migrationRunner->latest();
} catch (DatabaseException $e) {
$message = $e->getMessage();
if (strpos($message, 'already exists') === false) {
throw $e;
}
}
$initialBufferLevel = ob_get_level();
ob_start();
try {
$seeder = Database::seeder('tests');
$seeder->setSilent(true)->call('DBSeeder');
} finally {
while (ob_get_level() > $initialBufferLevel) {
ob_end_clean();
}
}
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/codeigniter4/framework/system/Test/bootstrap.php';
use CodeIgniter\Config\DotEnv;
use CodeIgniter\Database\Exceptions\DatabaseException;
use CodeIgniter\Database\MigrationRunner;
use Config\Database;
use Config\Migrations as MigrationsConfig;
if (defined('CLQMS_PHPUNIT_BOOTSTRAPPED') || ENVIRONMENT !== 'testing') {
return;
}
define('CLQMS_PHPUNIT_BOOTSTRAPPED', true);
(new DotEnv(ROOTPATH))->load();
$db = Database::connect('tests');
$forge = Database::forge('tests');
$db->query('SET FOREIGN_KEY_CHECKS=0');
foreach ($db->listTables() as $table) {
$forge->dropTable($table, true);
}
$db->query('SET FOREIGN_KEY_CHECKS=1');
$migrationsConfig = config(MigrationsConfig::class);
$migrationRunner = new MigrationRunner($migrationsConfig, 'tests');
try {
$migrationRunner->latest();
} catch (DatabaseException $e) {
$message = $e->getMessage();
if (strpos($message, 'already exists') === false) {
throw $e;
}
}
$initialBufferLevel = ob_get_level();
ob_start();
try {
$seeder = Database::seeder('tests');
$seeder->setSilent(true)->call('DBSeeder');
} finally {
while (ob_get_level() > $initialBufferLevel) {
ob_end_clean();
}
}