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 /cache
/project.local.yml /project.local.yml

View File

@ -1,14 +1,14 @@
Essential commands for CLQMS development (run from repo root on Windows PowerShell): Essential commands for CLQMS development (run from repo root on Windows PowerShell):
`composer install` install PHP dependencies before running CodeIgniter or tests. `composer install` install PHP dependencies before running CodeIgniter or tests.
`npm install` sync `package-lock.json` for tooling such as API docs bundler. `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`). `./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 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 migrate` / `php spark migrate:rollback` apply or roll back database migrations.
`php spark serve` lightweight dev server for the API while developing locally. `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. `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. `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. `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. `type <file>` or `Get-Content` view file contents when tools are not convenient.
Use these commands routinely after code changes, tests, or migrations. 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: 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 ...`). 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. 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. 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. 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. 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. 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. 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 # the name by which the project can be referenced within Serena
project_name: "clqms01-be" project_name: "clqms01-be"
# list of languages for which language servers are started; choose from: # list of languages for which language servers are started; choose from:
# al bash clojure cpp csharp # al bash clojure cpp csharp
# csharp_omnisharp dart elixir elm erlang # csharp_omnisharp dart elixir elm erlang
# fortran fsharp go groovy haskell # fortran fsharp go groovy haskell
# haxe java julia kotlin lua # haxe java julia kotlin lua
# markdown # markdown
# matlab nix pascal perl php # matlab nix pascal perl php
# php_phpactor powershell python python_jedi r # php_phpactor powershell python python_jedi r
# rego ruby ruby_solargraph rust scala # rego ruby ruby_solargraph rust scala
# swift terraform toml typescript typescript_vts # swift terraform toml typescript typescript_vts
# vue yaml zig # vue yaml zig
# (This list may be outdated. For the current list, see values of Language enum here: # (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 # 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.) # For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.)
# Note: # Note:
# - For C, use cpp # - For C, use cpp
# - For JavaScript, use typescript # - For JavaScript, use typescript
# - For Free Pascal/Lazarus, use pascal # - For Free Pascal/Lazarus, use pascal
# Special requirements: # Special requirements:
# Some languages require additional setup/installations. # Some languages require additional setup/installations.
# See here for details: https://oraios.github.io/serena/01-about/020_programming-languages.html#language-servers # 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. # 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. # 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. # Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored.
languages: languages:
- php - php
# the encoding used by text files in the project # 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 # For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings
encoding: "utf-8" encoding: "utf-8"
# line ending convention to use when writing source files. # line ending convention to use when writing source files.
# Possible values: unset (use global setting), "lf", "crlf", or "native" (platform default) # 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. # This does not affect Serena's own files (e.g. memories and configuration files), which always use native line endings.
line_ending: line_ending:
# The language backend to use for this project. # The language backend to use for this project.
# If not set, the global setting from serena_config.yml is used. # If not set, the global setting from serena_config.yml is used.
# Valid values: LSP, JetBrains # Valid values: LSP, JetBrains
# Note: the backend is fixed at startup. If a project with a different backend # Note: the backend is fixed at startup. If a project with a different backend
# is activated post-init, an error will be returned. # is activated post-init, an error will be returned.
language_backend: language_backend:
# whether to use project's .gitignore files to ignore files # whether to use project's .gitignore files to ignore files
ignore_all_files_in_gitignore: true ignore_all_files_in_gitignore: true
# advanced configuration option allowing to configure language server-specific options. # advanced configuration option allowing to configure language server-specific options.
# Maps the language key to the 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. # 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. # No documentation on options means no options are available.
ls_specific_settings: {} ls_specific_settings: {}
# list of additional paths to ignore in this project. # list of additional paths to ignore in this project.
# Same syntax as gitignore, so you can use * and **. # Same syntax as gitignore, so you can use * and **.
# Note: global ignored_paths from serena_config.yml are also applied additively. # Note: global ignored_paths from serena_config.yml are also applied additively.
ignored_paths: [] ignored_paths: []
# whether the project is in read-only mode # 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 # 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 # Added on 2025-04-18
read_only: false read_only: false
# list of tool names to exclude. # list of tool names to exclude.
# This extends the existing exclusions (e.g. from the global configuration) # This extends the existing exclusions (e.g. from the global configuration)
# #
# Below is the complete list of tools for convenience. # Below is the complete list of tools for convenience.
# To make sure you have the latest list of tools, and to view their descriptions, # To make sure you have the latest list of tools, and to view their descriptions,
# execute `uv run scripts/print_tool_overview.py`. # execute `uv run scripts/print_tool_overview.py`.
# #
# * `activate_project`: Activates a project based on the project name or path. # * `activate_project`: Activates a project based on the project name or path.
# * `check_onboarding_performed`: Checks whether project onboarding was already performed. # * `check_onboarding_performed`: Checks whether project onboarding was already performed.
# * `create_text_file`: Creates/overwrites a file in the project directory. # * `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, # * `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 # for example by saying that the information retrieved from a memory file is no longer correct
# or no longer relevant for the project. # or no longer relevant for the project.
# * `edit_memory`: Replaces content matching a regular expression in a memory. # * `edit_memory`: Replaces content matching a regular expression in a memory.
# * `execute_shell_command`: Executes a shell command. # * `execute_shell_command`: Executes a shell command.
# * `find_file`: Finds files in the given relative paths # * `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_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. # * `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_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. # * `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') # * `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. # 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_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. # * `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_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. # * `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). # * `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_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 # * `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 to the current task. You can infer whether the information
# is relevant from the memory file name. # is relevant from the memory file name.
# You should not read the same memory file multiple times in the same conversation. # 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 # * `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). # (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. # * `rename_symbol`: Renames a symbol throughout the codebase using language server refactoring capabilities.
# For JB, we use a separate tool. # For JB, we use a separate tool.
# * `replace_content`: Replaces content in a file (optionally using regular expressions). # * `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. # * `replace_symbol_body`: Replaces the full definition of a symbol using the language server backend.
# * `safe_delete_symbol`: # * `safe_delete_symbol`:
# * `search_for_pattern`: Performs a search for a pattern in the project. # * `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. # * `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. # The memory name should be meaningful.
excluded_tools: [] excluded_tools: []
# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default). # 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). # This extends the existing inclusions (e.g. from the global configuration).
included_optional_tools: [] included_optional_tools: []
# fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of 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. # This cannot be combined with non-empty excluded_tools or included_optional_tools.
fixed_tools: [] fixed_tools: []
# list of mode names to that are always to be included in the set of active modes # 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. # 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. # If the setting is undefined, the base_modes from the global configuration (serena_config.yml) apply.
# Otherwise, this setting overrides the global configuration. # Otherwise, this setting overrides the global configuration.
# Set this to [] to disable base modes for this project. # 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. # Set this to a list of mode names to always include the respective modes for this project.
base_modes: base_modes:
# list of mode names that are to be activated by default. # list of mode names that are to be activated by default.
# The full set of modes to be activated is base_modes + default_modes. # 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. # 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). # Otherwise, this overrides the setting from the global configuration (serena_config.yml).
# This setting can, in turn, be overridden by CLI parameters (--mode). # This setting can, in turn, be overridden by CLI parameters (--mode).
default_modes: default_modes:
# initial prompt for the project. It will always be given to the LLM upon activating the project # 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). # (contrary to the memories, which are loaded on demand).
initial_prompt: "" initial_prompt: ""
# time budget (seconds) per tool call for the retrieval of additional symbol information # time budget (seconds) per tool call for the retrieval of additional symbol information
# such as docstrings or parameter information. # such as docstrings or parameter information.
# This overrides the corresponding setting in the global configuration; see the documentation there. # This overrides the corresponding setting in the global configuration; see the documentation there.
# If null or missing, use the setting from the global configuration. # If null or missing, use the setting from the global configuration.
symbol_info_budget: symbol_info_budget:
# list of regex patterns which, when matched, mark a memory entry as readonly. # list of regex patterns which, when matched, mark a memory entry as readonly.
# Extends the list from the global configuration, merging the two lists. # Extends the list from the global configuration, merging the two lists.
read_only_memory_patterns: [] read_only_memory_patterns: []
# list of regex patterns for memories to completely ignore. # list of regex patterns for memories to completely ignore.
# Matching memories will not appear in list_memories or activate_project output # Matching memories will not appear in list_memories or activate_project output
# and cannot be accessed via read_memory or write_memory. # 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. # 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. # Extends the list from the global configuration, merging the two lists.
# Example: ["_archive/.*", "_episodes/.*"] # Example: ["_archive/.*", "_episodes/.*"]
ignored_memory_patterns: [] ignored_memory_patterns: []

306
AGENTS.md
View File

@ -1,153 +1,153 @@
# AGENTS.md - Code Guidelines for CLQMS # 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. > **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 ## Repository Snapshot
- `app/` holds controllers, models, filters, and traits wired through PSR-4 `App\` namespace. - `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. - `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. - 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. - Environment values, secrets, and database credentials live in `.env` but are never committed; treat the file as a reference for defaults.
--- ---
## Build, Lint & Test ## Build, Lint & Test
All commands run from the repository root. All commands run from the repository root.
```bash ```bash
# Run the entire PHPUnit suite # Run the entire PHPUnit suite
./vendor/bin/phpunit ./vendor/bin/phpunit
# Target a single test file (fast verification) # Target a single test file (fast verification)
./vendor/bin/phpunit tests/feature/Patients/PatientCreateTest.php ./vendor/bin/phpunit tests/feature/Patients/PatientCreateTest.php
# Run one test case by method # Run one test case by method
./vendor/bin/phpunit --filter testCreatePatientSuccess tests/feature/Patients/PatientCreateTest.php ./vendor/bin/phpunit --filter testCreatePatientSuccess tests/feature/Patients/PatientCreateTest.php
# Generate scaffolding (model, controller, migration) # Generate scaffolding (model, controller, migration)
php spark make:model <Name> php spark make:model <Name>
php spark make:controller <Name> php spark make:controller <Name>
php spark make:migration <name> php spark make:migration <name>
# Database migrations # Database migrations
php spark migrate php spark migrate
php spark migrate:rollback php spark migrate:rollback
# After OpenAPI edits # After OpenAPI edits
node public/bundle-api-docs.js node public/bundle-api-docs.js
``` ```
Use `php spark test --filter <Class>::<method>` when filtering more than one test file is cumbersome. Use `php spark test --filter <Class>::<method>` when filtering more than one test file is cumbersome.
--- ---
## Agent Rules Scan ## Agent Rules Scan
- No `.cursor/rules/*` or `.cursorrules` directory detected; continue without Cursor-specific constraints. - 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. - No `.github/copilot-instructions.md` present; Copilot behaviors revert to general GitHub defaults.
--- ---
## Coding Standards ## Coding Standards
### Language & Formatting ### Language & Formatting
- PHP 8.1+ is the baseline; enable `declare(strict_types=1)` at the top of new files when practical. - 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. - 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. - 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. - 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. - Keep methods under ~40 lines when possible; extract private helpers for repeated flows.
### Naming & Types ### Naming & Types
- Classes, controllers, libraries, and traits: PascalCase (e.g., `PatientImportController`). - Classes, controllers, libraries, and traits: PascalCase (e.g., `PatientImportController`).
- Methods, services, traits: camelCase (`fetchActivePatients`). - Methods, services, traits: camelCase (`fetchActivePatients`).
- Properties: camelCase for new code; legacy snake_case may persist but avoid new snake_case unless mirroring legacy columns. - Properties: camelCase for new code; legacy snake_case may persist but avoid new snake_case unless mirroring legacy columns.
- Constants: UPPER_SNAKE_CASE. - Constants: UPPER_SNAKE_CASE.
- DTOs/array shapes: Use descriptive names (`$patientInput`, `$validatedPayload`). - 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. - 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. - Prefer PHPDoc only when type inference fails (complex union or array shapes) but still keep method summaries concise.
### Imports & Structure ### Imports & Structure
- Namespace declarations at the very top followed by grouped `use` statements. - 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. - Import order: Core framework (`CodeIgniter`), then `App\`, then third-party packages (Firebase, Faker, etc.). Keep each group alphabetical.
- No inline `use` statements inside methods. - No inline `use` statements inside methods.
- Keep `use` statements de-duplicated; rely on IDE or `phpcbf` to reorder. - Keep `use` statements de-duplicated; rely on IDE or `phpcbf` to reorder.
### Controller Structure ### Controller Structure
- Controllers orchestrate request validation, delegates to services/models, and return `ResponseTrait` responses; avoid direct DB queries here. - 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\...`). - 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. - 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. - Always respond through `$this->respond()` or `$this->respondCreated()` so JSON structure stays consistent.
### Response & Error Handling ### Response & Error Handling
- All responses follow `{ status, message, data }`. `status` values: `success`, `failed`, or `error`. - All responses follow `{ status, message, data }`. `status` values: `success`, `failed`, or `error`.
- Use `$this->respondCreated()`, `$this->respondNoContent()`, or `$this->respond()` with explicit HTTP codes. - 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. - 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. - For validation failures, return HTTP 400 with detailed message; unauthorized access returns 401. Maintain parity with existing tests.
### Database & Transactions ### Database & Transactions
- Use Query Builder or Model methods; enable `use App\Models\BaseModel` which handles UTC conversions. - Use Query Builder or Model methods; enable `use App\Models\BaseModel` which handles UTC conversions.
- Always call `helper('utc')` when manipulating timestamps. - 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. - 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. - Run `checkDbError()` (existing helper) after saves when manual queries are necessary.
### Service Helpers & Libraries ### Service Helpers & Libraries
- Encapsulate complex lookups (ValueSet, encryption) inside `app/Libraries` or Traits. - Encapsulate complex lookups (ValueSet, encryption) inside `app/Libraries` or Traits.
- Reuse `App\Libraries\Lookups` for consistent label/value translations. - 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`. - Keep shared logic (e.g., response formatting, JWT decoding) inside Traits and import them via `use`.
### Testing & Coverage ### Testing & Coverage
- Place feature tests under `tests/Feature`, unit tests under `tests/Unit`. - 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`). - 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. - 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. - 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. - Run targeted tests during development, full suite before merging.
### Documentation & API Sync ### 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`. - 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. - 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. - Keep the controller-to-YAML mapping table updated to reflect new resources.
### Routing Conventions ### Routing Conventions
- Keep route definitions grouped inside `$routes->group('api/<resource>')` blocks in `app/Config/Routes.php`. - 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. - 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. - 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. - Document side effects (snapshots, audit logs) directly in the corresponding OpenAPI `paths` file.
### Environment & Secrets ### Environment & Secrets
- Use `.env` as the source of truth for database/jwt settings. Do not commit production credentials. - 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. - 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. - `JWT_SECRET` must be treated as sensitive and rotated via environment updates only.
### Workflows & Misc ### Workflows & Misc
- Use `php spark migrate`/`migrate:rollback` for schema changes. - Use `php spark migrate`/`migrate:rollback` for schema changes.
- For seeding or test fixtures, prefer factories (Faker) seeded in `tests/Support` when available. - 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. - Document major changes in `issues.md` or dedicated feature docs under `docs/` before merging.
### Security & Filters ### Security & Filters
- Apply the `auth` filter to every protected route, and keep `ApiKey` or other custom filters consolidated under `app/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. - 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. - 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. - Respond 401 for missing tokens, 403 when permissions fail, and log sanitized details for ops debugging.
### Legacy Field Naming & ValueSets ### Legacy Field Naming & ValueSets
- Databases use PascalCase columns such as `PatientID`, `NameFirst`, `CreatedAt`. Keep migration checks aware of these names. - 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'])`. - 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. - Prefer `App\Libraries\Lookups` or `app/Traits/ValueSetTrait` to avoid ad-hoc mappings.
### Nested Data Handling ### Nested Data Handling
- For entities that carry related collections (`PatIdt`, `PatCom`, `PatAtt`), extract nested arrays before filtering and validating. - 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. - Use transactions whenever multi-table inserts/updates occur so orphan rows are avoided.
- Guard against empty/null arrays by normalizing to `[]` before iterating. - Guard against empty/null arrays by normalizing to `[]` before iterating.
### Observability & Logging ### Observability & Logging
- Use `log_message('info', ...)` for happy-path checkpoints and `'error'` for catch-all failures. - 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. - 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. - Keep `writable/logs` clean by rotating or pruning stale log files with automation outside the repo.
--- ---
## Final Notes for Agents ## Final Notes for Agents
- This repo has no UI layer; focus exclusively on REST interactions. - 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. - 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. - 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). - 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 <?php
namespace App\Controllers\Audit; namespace App\Controllers\Audit;
use App\Controllers\BaseController; use App\Controllers\BaseController;
use App\Services\AuditLogService; use App\Services\AuditLogService;
use App\Traits\ResponseTrait; use App\Traits\ResponseTrait;
use CodeIgniter\HTTP\ResponseInterface; use CodeIgniter\HTTP\ResponseInterface;
use InvalidArgumentException; use InvalidArgumentException;
class AuditLogController extends BaseController class AuditLogController extends BaseController
{ {
use ResponseTrait; use ResponseTrait;
private AuditLogService $auditLogService; private AuditLogService $auditLogService;
public function __construct() public function __construct()
{ {
$this->auditLogService = new AuditLogService(); $this->auditLogService = new AuditLogService();
} }
public function index(): ResponseInterface public function index(): ResponseInterface
{ {
$filters = [ $filters = [
'table' => $this->request->getGet('table'), 'table' => $this->request->getGet('table'),
'rec_id' => $this->request->getGet('rec_id') ?? $this->request->getGet('recId'), 'rec_id' => $this->request->getGet('rec_id') ?? $this->request->getGet('recId'),
'event_id' => $this->request->getGet('event_id') ?? $this->request->getGet('eventId'), 'event_id' => $this->request->getGet('event_id') ?? $this->request->getGet('eventId'),
'activity_id' => $this->request->getGet('activity_id') ?? $this->request->getGet('activityId'), 'activity_id' => $this->request->getGet('activity_id') ?? $this->request->getGet('activityId'),
'from' => $this->request->getGet('from'), 'from' => $this->request->getGet('from'),
'to' => $this->request->getGet('to'), 'to' => $this->request->getGet('to'),
'search' => $this->request->getGet('search'), 'search' => $this->request->getGet('search'),
'page' => $this->request->getGet('page'), 'page' => $this->request->getGet('page'),
'perPage' => $this->request->getGet('perPage') ?? $this->request->getGet('per_page'), 'perPage' => $this->request->getGet('perPage') ?? $this->request->getGet('per_page'),
]; ];
try { try {
$payload = $this->auditLogService->fetchLogs($filters); $payload = $this->auditLogService->fetchLogs($filters);
return $this->respond([ return $this->respond([
'status' => 'success', 'status' => 'success',
'message' => 'Audit logs retrieved successfully', 'message' => 'Audit logs retrieved successfully',
'data' => $payload, 'data' => $payload,
], 200); ], 200);
} catch (InvalidArgumentException $e) { } catch (InvalidArgumentException $e) {
return $this->respond([ return $this->respond([
'status' => 'failed', 'status' => 'failed',
'message' => $e->getMessage(), 'message' => $e->getMessage(),
'data' => null, 'data' => null,
], 400); ], 400);
} catch (\Throwable $e) { } catch (\Throwable $e) {
log_message('error', 'AuditLogController::index error: ' . $e->getMessage()); log_message('error', 'AuditLogController::index error: ' . $e->getMessage());
return $this->respond([ return $this->respond([
'status' => 'failed', 'status' => 'failed',
'message' => 'Unable to retrieve audit logs', 'message' => 'Unable to retrieve audit logs',
'data' => null, 'data' => null,
], 500); ], 500);
} }
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

69
composer.lock generated
View File

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

12
package-lock.json generated
View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -156,6 +156,7 @@ class ContactPatchTest extends CIUnitTestCase
[ [
'ContactDetID' => $keepDetail['ContactDetID'], 'ContactDetID' => $keepDetail['ContactDetID'],
'JobTitle' => 'Senior Doctor', 'JobTitle' => 'Senior Doctor',
'ContactStartDate' => '2026-04-16T07:22:44.000Z',
], ],
], ],
'created' => [ 'created' => [
@ -182,8 +183,19 @@ class ContactPatchTest extends CIUnitTestCase
$this->assertCount(2, $afterData['Details']); $this->assertCount(2, $afterData['Details']);
$detailIds = array_column($afterData['Details'], 'ContactDetID'); $detailIds = array_column($afterData['Details'], 'ContactDetID');
$this->assertContains($keepDetail['ContactDetID'], $detailIds); $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->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 <?php
namespace Tests\Feature\Location; namespace Tests\Feature\Location;
use CodeIgniter\Test\FeatureTestTrait; use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase; use CodeIgniter\Test\CIUnitTestCase;
use Firebase\JWT\JWT; use Firebase\JWT\JWT;
class LocationPatchTest extends CIUnitTestCase class LocationPatchTest extends CIUnitTestCase
{ {
use FeatureTestTrait; use FeatureTestTrait;
protected string $token; protected string $token;
protected string $endpoint = 'api/location'; protected string $endpoint = 'api/location';
protected function setUp(): void protected function setUp(): void
{ {
parent::setUp(); parent::setUp();
$key = getenv('JWT_SECRET') ?: 'my-secret-key'; $key = getenv('JWT_SECRET') ?: 'my-secret-key';
$payload = [ $payload = [
'iss' => 'localhost', 'iss' => 'localhost',
'aud' => 'localhost', 'aud' => 'localhost',
'iat' => time(), 'iat' => time(),
'nbf' => time(), 'nbf' => time(),
'exp' => time() + 3600, 'exp' => time() + 3600,
'uid' => 1, 'uid' => 1,
'email' => 'admin@admin.com', 'email' => 'admin@admin.com',
]; ];
$this->token = JWT::encode($payload, $key, 'HS256'); $this->token = JWT::encode($payload, $key, 'HS256');
} }
private function authHeaders(): array private function authHeaders(): array
{ {
return ['Cookie' => 'token=' . $this->token]; return ['Cookie' => 'token=' . $this->token];
} }
private function createLocation(array $data = []): array private function createLocation(array $data = []): array
{ {
$payload = array_merge([ $payload = array_merge([
'LocCode' => 'LC' . substr(uniqid(), -4), 'LocCode' => 'LC' . substr(uniqid(), -4),
'LocFull' => 'Test Location ' . uniqid(), 'LocFull' => 'Test Location ' . uniqid(),
], $data); ], $data);
$response = $this->withHeaders($this->authHeaders()) $response = $this->withHeaders($this->authHeaders())
->withBodyFormat('json') ->withBodyFormat('json')
->call('post', $this->endpoint, $payload); ->call('post', $this->endpoint, $payload);
$response->assertStatus(201); $response->assertStatus(201);
$decoded = json_decode($response->getJSON(), true); $decoded = json_decode($response->getJSON(), true);
$locationId = $decoded['data']['LocationID']; $locationId = $decoded['data']['LocationID'];
$show = $this->withHeaders($this->authHeaders()) $show = $this->withHeaders($this->authHeaders())
->call('get', "{$this->endpoint}/{$locationId}"); ->call('get', "{$this->endpoint}/{$locationId}");
$show->assertStatus(200); $show->assertStatus(200);
$showData = json_decode($show->getJSON(), true)['data']; $showData = json_decode($show->getJSON(), true)['data'];
return $showData; return $showData;
} }
public function testPartialUpdateLocationSuccess() public function testPartialUpdateLocationSuccess()
{ {
$location = $this->createLocation(); $location = $this->createLocation();
$id = $location['LocationID']; $id = $location['LocationID'];
$patch = $this->withHeaders($this->authHeaders()) $patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json') ->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['LocFull' => 'Updated Location']); ->call('patch', "{$this->endpoint}/{$id}", ['LocFull' => 'Updated Location']);
$patch->assertStatus(200); $patch->assertStatus(200);
$patchData = json_decode($patch->getJSON(), true); $patchData = json_decode($patch->getJSON(), true);
$this->assertEquals('success', $patchData['status']); $this->assertEquals('success', $patchData['status']);
$show = $this->withHeaders($this->authHeaders())->call('get', "{$this->endpoint}/{$id}"); $show = $this->withHeaders($this->authHeaders())->call('get', "{$this->endpoint}/{$id}");
$show->assertStatus(200); $show->assertStatus(200);
$showData = json_decode($show->getJSON(), true)['data']; $showData = json_decode($show->getJSON(), true)['data'];
$this->assertEquals('Updated Location', $showData['LocFull']); $this->assertEquals('Updated Location', $showData['LocFull']);
$this->assertEquals($location['LocCode'], $showData['LocCode']); $this->assertEquals($location['LocCode'], $showData['LocCode']);
} }
public function testPartialUpdateLocationNotFound() public function testPartialUpdateLocationNotFound()
{ {
$patch = $this->withHeaders($this->authHeaders()) $patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json') ->withBodyFormat('json')
->call('patch', "{$this->endpoint}/999999", ['LocFull' => 'Updated']); ->call('patch', "{$this->endpoint}/999999", ['LocFull' => 'Updated']);
$patch->assertStatus(404); $patch->assertStatus(404);
} }
public function testPartialUpdateLocationInvalidId() public function testPartialUpdateLocationInvalidId()
{ {
$patch = $this->withHeaders($this->authHeaders()) $patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json') ->withBodyFormat('json')
->call('patch', "{$this->endpoint}/invalid", ['LocFull' => 'Updated']); ->call('patch', "{$this->endpoint}/invalid", ['LocFull' => 'Updated']);
$patch->assertStatus(400); $patch->assertStatus(400);
} }
public function testPartialUpdateLocationEmptyPayload() public function testPartialUpdateLocationEmptyPayload()
{ {
$location = $this->createLocation(); $location = $this->createLocation();
$id = $location['LocationID']; $id = $location['LocationID'];
$patch = $this->withHeaders($this->authHeaders()) $patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json') ->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", []); ->call('patch', "{$this->endpoint}/{$id}", []);
$patch->assertStatus(400); $patch->assertStatus(400);
} }
public function testPartialUpdateLocationSingleField() public function testPartialUpdateLocationSingleField()
{ {
$location = $this->createLocation(); $location = $this->createLocation();
$id = $location['LocationID']; $id = $location['LocationID'];
$patch = $this->withHeaders($this->authHeaders()) $patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json') ->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['LocCode' => 'LC' . substr(uniqid(), -4)]); ->call('patch', "{$this->endpoint}/{$id}", ['LocCode' => 'LC' . substr(uniqid(), -4)]);
$patch->assertStatus(200); $patch->assertStatus(200);
$showData = json_decode($this->withHeaders($this->authHeaders()) $showData = json_decode($this->withHeaders($this->authHeaders())
->call('get', "{$this->endpoint}/{$id}") ->call('get', "{$this->endpoint}/{$id}")
->getJSON(), true)['data']; ->getJSON(), true)['data'];
$this->assertNotEquals($location['LocCode'], $showData['LocCode']); $this->assertNotEquals($location['LocCode'], $showData['LocCode']);
$this->assertEquals($location['LocFull'], $showData['LocFull']); $this->assertEquals($location['LocFull'], $showData['LocFull']);
} }
public function testPartialUpdateLocationAddressField() public function testPartialUpdateLocationAddressField()
{ {
$location = $this->createLocation(); $location = $this->createLocation();
$id = $location['LocationID']; $id = $location['LocationID'];
$patch = $this->withHeaders($this->authHeaders()) $patch = $this->withHeaders($this->authHeaders())
->withBodyFormat('json') ->withBodyFormat('json')
->call('patch', "{$this->endpoint}/{$id}", ['Street1' => '123 Market St']); ->call('patch', "{$this->endpoint}/{$id}", ['Street1' => '123 Market St']);
$patch->assertStatus(200); $patch->assertStatus(200);
$showData = json_decode($this->withHeaders($this->authHeaders()) $showData = json_decode($this->withHeaders($this->authHeaders())
->call('get', "{$this->endpoint}/{$id}") ->call('get', "{$this->endpoint}/{$id}")
->getJSON(), true)['data']; ->getJSON(), true)['data'];
$this->assertEquals('123 Market St', $showData['Street1']); $this->assertEquals('123 Market St', $showData['Street1']);
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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