From d3668fe2b3f5ceb8faf9b24de0872def07c35f34 Mon Sep 17 00:00:00 2001 From: mahdahar <89adham@gmail.com> Date: Tue, 24 Feb 2026 16:53:36 +0700 Subject: [PATCH] feat: add equipment list management API with CRUD operations --- AGENTS.md | 29 ++ app/Config/Routes.php | 9 + .../EquipmentListController.php | 113 ++++++++ .../2026-01-01-000001_CreateValueSet.php | 11 + .../2026-01-01-000002_CreateOrganization.php | 15 + ...6-01-01-000005_CreateLabInfrastructure.php | 32 +-- app/Database/Seeds/OrganizationSeeder.php | 33 +++ app/Database/Seeds/TestSeeder.php | 165 ++++++++++- .../Infrastructure/EquipmentListModel.php | 65 +++++ public/api-docs.bundled.yaml | 270 ++++++++++++++++++ public/api-docs.yaml | 6 + public/components/schemas/equipmentlist.yaml | 47 +++ public/paths/equipmentlist.yaml | 212 ++++++++++++++ 13 files changed, 966 insertions(+), 41 deletions(-) create mode 100644 app/Controllers/Infrastructure/EquipmentListController.php create mode 100644 app/Models/Infrastructure/EquipmentListModel.php create mode 100644 public/components/schemas/equipmentlist.yaml create mode 100644 public/paths/equipmentlist.yaml diff --git a/AGENTS.md b/AGENTS.md index 9cfc69e..73ef934 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -228,6 +228,35 @@ $routes->group('api/patient', function ($routes) { ## Project-Specific Conventions +### API Documentation Sync +**CRITICAL**: When updating any controller, you MUST also update the corresponding OpenAPI YAML documentation: + +- **Paths**: `public/paths/.yaml` (e.g., `patients.yaml`, `orders.yaml`) +- **Schemas**: `public/components/schemas/.yaml` +- **Main file**: `public/api-docs.yaml` (for tags and schema references) + +**After updating YAML files**, regenerate the bundled documentation: +```bash +node public/bundle-api-docs.js +``` + +This produces `public/api-docs.bundled.yaml` which is used by Swagger UI/Redoc. + +### Controller-to-YAML Mapping +| Controller | YAML Path File | YAML Schema File | +|-----------|----------------|------------------| +| `PatientController` | `paths/patients.yaml` | `components/schemas/patient.yaml` | +| `PatVisitController` | `paths/patient-visits.yaml` | `components/schemas/patient-visit.yaml` | +| `OrderTestController` | `paths/orders.yaml` | `components/schemas/orders.yaml` | +| `SpecimenController` | `paths/specimen.yaml` | `components/schemas/specimen.yaml` | +| `TestsController` | `paths/tests.yaml` | `components/schemas/tests.yaml` | +| `AuthController` | `paths/authentication.yaml` | `components/schemas/authentication.yaml` | +| `ResultController` | `paths/results.yaml` | `components/schemas/*.yaml` | +| `EdgeController` | `paths/edge-api.yaml` | `components/schemas/edge-api.yaml` | +| `LocationController` | `paths/locations.yaml` | `components/schemas/master-data.yaml` | +| `ValueSetController` | `paths/valuesets.yaml` | `components/schemas/valuesets.yaml` | +| `ContactController` | `paths/contact.yaml` | (inline schemas) | + ### Legacy Field Naming Database uses PascalCase columns: `PatientID`, `NameFirst`, `Birthdate`, `CreatedAt`, `UpdatedAt` diff --git a/app/Config/Routes.php b/app/Config/Routes.php index 3edc63e..e161742 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -208,6 +208,15 @@ $routes->group('api', function ($routes) { }); }); + // Infrastructure + $routes->group('equipmentlist', function ($routes) { + $routes->get('/', 'Infrastructure\EquipmentListController::index'); + $routes->get('(:num)', 'Infrastructure\EquipmentListController::show/$1'); + $routes->post('/', 'Infrastructure\EquipmentListController::create'); + $routes->patch('/', 'Infrastructure\EquipmentListController::update'); + $routes->delete('/', 'Infrastructure\EquipmentListController::delete'); + }); + // Specimen $routes->group('specimen', function ($routes) { // Container aliases - 'container' and 'containerdef' both point to ContainerDefController diff --git a/app/Controllers/Infrastructure/EquipmentListController.php b/app/Controllers/Infrastructure/EquipmentListController.php new file mode 100644 index 0000000..5f542b8 --- /dev/null +++ b/app/Controllers/Infrastructure/EquipmentListController.php @@ -0,0 +1,113 @@ +db = \Config\Database::connect(); + $this->model = new EquipmentListModel(); + } + + public function index() { + $filter = [ + 'IEID' => $this->request->getVar('IEID'), + 'InstrumentName' => $this->request->getVar('InstrumentName'), + 'DepartmentID' => $this->request->getVar('DepartmentID'), + 'WorkstationID' => $this->request->getVar('WorkstationID'), + 'Enable' => $this->request->getVar('Enable'), + ]; + + $rows = $this->model->getEquipmentLists($filter); + + if (empty($rows)) { + return $this->respond([ + 'status' => 'success', + 'message' => 'no Data.', + 'data' => [] + ], 200); + } + + return $this->respond([ + 'status' => 'success', + 'message' => 'fetch success', + 'data' => $rows + ], 200); + } + + public function show($EID = null) { + $row = $this->model->getEquipmentList($EID); + + if (empty($row)) { + return $this->respond([ + 'status' => 'success', + 'message' => 'no Data.', + 'data' => null + ], 200); + } + + return $this->respond([ + 'status' => 'success', + 'message' => 'fetch success', + 'data' => $row + ], 200); + } + + public function create() { + $input = $this->request->getJSON(true); + + try { + $EID = $this->model->insert($input, true); + return $this->respondCreated([ + 'status' => 'success', + 'message' => 'data created successfully', + 'data' => $EID + ], 201); + } catch (\Throwable $e) { + return $this->failServerError('Something went wrong: ' . $e->getMessage()); + } + } + + public function update() { + $input = $this->request->getJSON(true); + + try { + $EID = $input['EID']; + $this->model->update($EID, $input); + return $this->respond([ + 'status' => 'success', + 'message' => 'data updated successfully', + 'data' => $EID + ], 200); + } catch (\Throwable $e) { + return $this->failServerError('Something went wrong: ' . $e->getMessage()); + } + } + + public function delete() { + try { + $input = $this->request->getJSON(true); + $EID = $input['EID']; + + if (!$EID) { + return $this->failValidationErrors('EID is required.'); + } + + $this->model->delete($EID); + return $this->respondDeleted([ + 'status' => 'success', + 'message' => "{$EID} deleted successfully." + ]); + } catch (\Throwable $e) { + return $this->failServerError('Something went wrong: ' . $e->getMessage()); + } + } +} diff --git a/app/Database/Migrations/2026-01-01-000001_CreateValueSet.php b/app/Database/Migrations/2026-01-01-000001_CreateValueSet.php index 68c3b28..e50ea77 100644 --- a/app/Database/Migrations/2026-01-01-000001_CreateValueSet.php +++ b/app/Database/Migrations/2026-01-01-000001_CreateValueSet.php @@ -80,9 +80,20 @@ class CreateLookups extends Migration { ]); $this->forge->addKey('SpecialtyID', true); $this->forge->createTable('medicalspecialty'); + + $this->forge->addField([ + 'AreaGeoID' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'auto_increment' => true], + 'AreaCode' => ['type' => 'varchar', 'constraint' => 20, 'null' => true], + 'Class' => ['type' => 'int', 'null' => true], + 'AreaName' => ['type' => 'VARCHAR', 'constraint' => 100, 'null' => false], + 'Parent' => ['type' => 'int', 'null' => true], + ]); + $this->forge->addKey('AreaGeoID', true); + $this->forge->createTable('areageo'); } public function down() { + $this->forge->dropTable('areageo'); $this->forge->dropTable('medicalspecialty'); $this->forge->dropTable('occupation'); $this->forge->dropTable('containerdef'); diff --git a/app/Database/Migrations/2026-01-01-000002_CreateOrganization.php b/app/Database/Migrations/2026-01-01-000002_CreateOrganization.php index a50bf54..d6b849c 100644 --- a/app/Database/Migrations/2026-01-01-000002_CreateOrganization.php +++ b/app/Database/Migrations/2026-01-01-000002_CreateOrganization.php @@ -98,9 +98,24 @@ class CreateOrganization extends Migration { ]); $this->forge->addKey('DepartmentID', true); $this->forge->createTable('department'); + + $this->forge->addField([ + 'WorkstationID' => ['type' => 'int', 'unsigned' => true, 'auto_increment'=> true], + 'DepartmentID' => ['type' => 'int', 'null'=> false], + 'WorkstationCode' => ['type' => 'varchar', 'constraint'=>10, 'null'=> false], + 'WorkstationName' => ['type' => 'varchar', 'constraint'=> 150, 'null'=> true], + 'Type' => ['type' => 'VARCHAR', 'constraint' => 10, 'null'=> true], + 'LinkTo' => ['type' => 'int', 'null'=> true], + 'Enable' => ['type' => 'VARCHAR', 'constraint' => 10, 'null'=> true], + 'CreateDate' => ['type'=>'DATETIME', 'null' => true], + 'EndDate' => ['type'=>'DATETIME', 'null' => true] + ]); + $this->forge->addKey('WorkstationID', true); + $this->forge->createTable('workstation'); } public function down() { + $this->forge->dropTable('workstation'); $this->forge->dropTable('department'); $this->forge->dropTable('discipline'); $this->forge->dropTable('locationaddress'); diff --git a/app/Database/Migrations/2026-01-01-000005_CreateLabInfrastructure.php b/app/Database/Migrations/2026-01-01-000005_CreateLabInfrastructure.php index ba6b29d..a090551 100644 --- a/app/Database/Migrations/2026-01-01-000005_CreateLabInfrastructure.php +++ b/app/Database/Migrations/2026-01-01-000005_CreateLabInfrastructure.php @@ -7,30 +7,18 @@ use CodeIgniter\Database\Migration; class CreateLabInfrastructure extends Migration { public function up() { $this->forge->addField([ - 'WorkstationID' => ['type' => 'int', 'unsigned' => true, 'auto_increment'=> true], - 'DepartmentID' => ['type' => 'int', 'null'=> false], - 'WorkstationCode' => ['type' => 'varchar', 'constraint'=>10, 'null'=> false], - 'WorkstationName' => ['type' => 'varchar', 'constraint'=> 150, 'null'=> true], - 'Type' => ['type' => 'VARCHAR', 'constraint' => 10, 'null'=> true], - 'LinkTo' => ['type' => 'int', 'null'=> true], - 'Enable' => ['type' => 'VARCHAR', 'constraint' => 10, 'null'=> true], - 'CreateDate' => ['type'=>'DATETIME', 'null' => true], - 'EndDate' => ['type'=>'DATETIME', 'null' => true] - ]); - $this->forge->addKey('WorkstationID', true); - $this->forge->createTable('workstation'); - - $this->forge->addField([ - 'EquipmentID' => ['type' => 'int', 'unsigned' => true, 'auto_increment'=> true], + 'EID' => ['type' => 'int', 'unsigned' => true, 'auto_increment'=> true], + 'IEID' => ['type' => 'varchar', 'constraint' => 50], 'DepartmentID' => ['type' => 'int', 'constraint'=> 10, 'null'=> false], 'InstrumentID' => ['type' => 'varchar', 'constraint'=> 150, 'null'=> true], 'InstrumentName' => ['type' => 'varchar', 'constraint'=> 150, 'null'=> true], + 'WorkstationID' => ['type' => 'int', 'unsigned' => true ], 'Enable' => ['type' => 'bit', 'null'=> false], 'EquipmentRole' => ['type' => 'varchar', 'constraint' => 1, 'null'=> false], 'CreateDate' => ['type'=>'DATETIME', 'null' => true], 'EndDate' => ['type'=>'DATETIME', 'null' => true] ]); - $this->forge->addKey('EquipmentID', true); + $this->forge->addKey('EID', true); $this->forge->createTable('equipmentlist'); $this->forge->addField([ @@ -68,23 +56,11 @@ class CreateLabInfrastructure extends Migration { ]); $this->forge->addKey('EquipmentID', true); $this->forge->createTable('devicelist'); - - $this->forge->addField([ - 'AreaGeoID' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'auto_increment' => true], - 'AreaCode' => ['type' => 'varchar', 'constraint' => 20, 'null' => true], - 'Class' => ['type' => 'int', 'null' => true], - 'AreaName' => ['type' => 'VARCHAR', 'constraint' => 100, 'null' => false], - 'Parent' => ['type' => 'int', 'null' => true], - ]); - $this->forge->addKey('AreaGeoID', true); - $this->forge->createTable('areageo'); } public function down() { - $this->forge->dropTable('areageo'); $this->forge->dropTable('devicelist'); $this->forge->dropTable('comparameters'); $this->forge->dropTable('equipmentlist'); - $this->forge->dropTable('workstation'); } } diff --git a/app/Database/Seeds/OrganizationSeeder.php b/app/Database/Seeds/OrganizationSeeder.php index afd08eb..c7076d4 100644 --- a/app/Database/Seeds/OrganizationSeeder.php +++ b/app/Database/Seeds/OrganizationSeeder.php @@ -64,5 +64,38 @@ class OrganizationSeeder extends Seeder ['WorkstationID' => '9', 'DepartmentID' => '6', 'WorkstationCode' => 'UMAN', 'WorkstationName' => 'Urin Manual', 'Type' => '0', 'LinkTo' => '', 'Enable' => '1', 'CreateDate' => "$now"], ]; $this->db->table('workstation')->insertBatch($data); + + // Equipment/Instruments for each workstation + $data = [ + // Hematology Auto (WorkstationID: 1) + ['EID' => 1, 'IEID' => 'EQ-HEM-001', 'DepartmentID' => '1', 'InstrumentID' => 'SYSMEX_XN1000', 'InstrumentName' => 'Sysmex XN-1000 Hematology Analyzer', 'WorkstationID' => 1, 'Enable' => 1, 'EquipmentRole' => 'A', 'CreateDate' => "$now"], + ['EID' => 2, 'IEID' => 'EQ-HEM-002', 'DepartmentID' => '1', 'InstrumentID' => 'SYSMEX_XS1000', 'InstrumentName' => 'Sysmex XS-1000i', 'WorkstationID' => 1, 'Enable' => 1, 'EquipmentRole' => 'A', 'CreateDate' => "$now"], + // Hematology Backup (WorkstationID: 2) + ['EID' => 3, 'IEID' => 'EQ-HEM-003', 'DepartmentID' => '1', 'InstrumentID' => 'SYSMEX_XN550', 'InstrumentName' => 'Sysmex XN-550 Backup', 'WorkstationID' => 2, 'Enable' => 1, 'EquipmentRole' => 'B', 'CreateDate' => "$now"], + ['EID' => 4, 'IEID' => 'EQ-HEM-004', 'DepartmentID' => '1', 'InstrumentID' => 'MANUAL_DIFF', 'InstrumentName' => 'Manual Differential Counter', 'WorkstationID' => 2, 'Enable' => 1, 'EquipmentRole' => 'M', 'CreateDate' => "$now"], + // Chemistry Auto (WorkstationID: 3) + ['EID' => 5, 'IEID' => 'EQ-CHEM-001', 'DepartmentID' => '3', 'InstrumentID' => 'COBAS_C501', 'InstrumentName' => 'Roche Cobas C501 Chemistry Analyzer', 'WorkstationID' => 3, 'Enable' => 1, 'EquipmentRole' => 'A', 'CreateDate' => "$now"], + ['EID' => 6, 'IEID' => 'EQ-CHEM-002', 'DepartmentID' => '3', 'InstrumentID' => 'COBAS_C311', 'InstrumentName' => 'Roche Cobas C311', 'WorkstationID' => 3, 'Enable' => 1, 'EquipmentRole' => 'A', 'CreateDate' => "$now"], + // Chemistry Backup (WorkstationID: 4) + ['EID' => 7, 'IEID' => 'EQ-CHEM-003', 'DepartmentID' => '3', 'InstrumentID' => 'COBAS_E411', 'InstrumentName' => 'Roche Cobas E411 Immunoassay', 'WorkstationID' => 4, 'Enable' => 1, 'EquipmentRole' => 'B', 'CreateDate' => "$now"], + // Chemistry Manual (WorkstationID: 5) + ['EID' => 8, 'IEID' => 'EQ-CHEM-004', 'DepartmentID' => '3', 'InstrumentID' => 'SPEC_MANUAL', 'InstrumentName' => 'Spectrophotometer Manual', 'WorkstationID' => 5, 'Enable' => 1, 'EquipmentRole' => 'M', 'CreateDate' => "$now"], + // Immunology Auto (WorkstationID: 6) + ['EID' => 9, 'IEID' => 'EQ-IMM-001', 'DepartmentID' => '4', 'InstrumentID' => 'ARCH_I2000', 'InstrumentName' => 'Architect i2000 Immunoassay', 'WorkstationID' => 6, 'Enable' => 1, 'EquipmentRole' => 'A', 'CreateDate' => "$now"], + ['EID' => 10, 'IEID' => 'EQ-IMM-002', 'DepartmentID' => '4', 'InstrumentID' => 'COBAS_E601', 'InstrumentName' => 'Roche Cobas E601', 'WorkstationID' => 6, 'Enable' => 1, 'EquipmentRole' => 'A', 'CreateDate' => "$now"], + // Immunology Manual (WorkstationID: 7) + ['EID' => 11, 'IEID' => 'EQ-IMM-003', 'DepartmentID' => '4', 'InstrumentID' => 'ELISA_READER', 'InstrumentName' => 'ELISA Plate Reader', 'WorkstationID' => 7, 'Enable' => 1, 'EquipmentRole' => 'M', 'CreateDate' => "$now"], + ['EID' => 12, 'IEID' => 'EQ-IMM-004', 'DepartmentID' => '4', 'InstrumentID' => 'FLUORIMETER', 'InstrumentName' => 'Fluorimeter Manual', 'WorkstationID' => 7, 'Enable' => 1, 'EquipmentRole' => 'M', 'CreateDate' => "$now"], + // Urinalysis Auto (WorkstationID: 8) + ['EID' => 13, 'IEID' => 'EQ-URI-001', 'DepartmentID' => '6', 'InstrumentID' => 'URISYS_1100', 'InstrumentName' => 'Urisys 1100 Urine Analyzer', 'WorkstationID' => 8, 'Enable' => 1, 'EquipmentRole' => 'A', 'CreateDate' => "$now"], + ['EID' => 14, 'IEID' => 'EQ-URI-002', 'DepartmentID' => '6', 'InstrumentID' => 'CLINITEK', 'InstrumentName' => 'Clinitek Urine Chemistry', 'WorkstationID' => 8, 'Enable' => 1, 'EquipmentRole' => 'A', 'CreateDate' => "$now"], + // Urinalysis Manual (WorkstationID: 9) + ['EID' => 15, 'IEID' => 'EQ-URI-003', 'DepartmentID' => '6', 'InstrumentID' => 'MICROSCOPE', 'InstrumentName' => 'Microscope Manual', 'WorkstationID' => 9, 'Enable' => 1, 'EquipmentRole' => 'M', 'CreateDate' => "$now"], + ['EID' => 16, 'IEID' => 'EQ-URI-004', 'DepartmentID' => '6', 'InstrumentID' => 'CENTRIFUGE', 'InstrumentName' => 'Centrifuge Urine', 'WorkstationID' => 9, 'Enable' => 1, 'EquipmentRole' => 'M', 'CreateDate' => "$now"], + // Additional equipment (disabled) + ['EID' => 17, 'IEID' => 'EQ-HEM-005', 'DepartmentID' => '1', 'InstrumentID' => 'OLD_ANALYZER', 'InstrumentName' => 'Old Hematology Analyzer', 'WorkstationID' => 1, 'Enable' => 0, 'EquipmentRole' => 'B', 'CreateDate' => "$now"], + ['EID' => 18, 'IEID' => 'EQ-CHEM-005', 'DepartmentID' => '3', 'InstrumentID' => 'MAINTENANCE', 'InstrumentName' => 'Chemistry Analyzer Under Maintenance', 'WorkstationID' => 3, 'Enable' => 0, 'EquipmentRole' => 'B', 'CreateDate' => "$now"], + ]; + $this->db->table('equipmentlist')->insertBatch($data); } } diff --git a/app/Database/Seeds/TestSeeder.php b/app/Database/Seeds/TestSeeder.php index e4543f1..9dbedde 100644 --- a/app/Database/Seeds/TestSeeder.php +++ b/app/Database/Seeds/TestSeeder.php @@ -225,7 +225,11 @@ class TestSeeder extends Seeder $testMaps = []; - // Hematology tests → HAUTO workstation + EDTA container + // ============================================ + // SITE → WORKSTATION mappings (no ConDefID) + // ============================================ + + // Hematology tests → HAUTO workstation $hematologyTests = ['HB', 'HCT', 'RBC', 'WBC', 'PLT', 'MCV', 'MCH', 'MCHC']; foreach ($hematologyTests as $testCode) { $testMaps[] = [ @@ -236,14 +240,14 @@ class TestSeeder extends Seeder 'HostTestName' => $testCode, 'ClientType' => 'WORKSTATION', 'ClientID' => (string)$wsHAuto, - 'ConDefID' => $conEDTA, + 'ConDefID' => null, 'ClientTestCode' => $testCode, 'ClientTestName' => $testCode, 'CreateDate' => $now ]; } - // Chemistry tests → CAUTO workstation + SST container + // Chemistry tests → CAUTO workstation $chemistryTests = ['GLU', 'CREA', 'UREA', 'SGOT', 'SGPT', 'CHOL', 'TG', 'HDL', 'LDL']; foreach ($chemistryTests as $testCode) { $testMaps[] = [ @@ -254,14 +258,14 @@ class TestSeeder extends Seeder 'HostTestName' => $testCode, 'ClientType' => 'WORKSTATION', 'ClientID' => (string)$wsCAuto, - 'ConDefID' => $conSST, + 'ConDefID' => null, 'ClientTestCode' => $testCode, 'ClientTestName' => $testCode, 'CreateDate' => $now ]; } - // Calculated chemistry tests → CAUTO workstation + SST container (inherited from parents) + // Calculated chemistry tests → CAUTO workstation $calcChemistryTests = ['EGFR', 'LDLCALC']; foreach ($calcChemistryTests as $testCode) { $testMaps[] = [ @@ -272,14 +276,14 @@ class TestSeeder extends Seeder 'HostTestName' => $testCode, 'ClientType' => 'WORKSTATION', 'ClientID' => (string)$wsCAuto, - 'ConDefID' => $conSST, + 'ConDefID' => null, 'ClientTestCode' => $testCode, 'ClientTestName' => $testCode, 'CreateDate' => $now ]; } - // Urinalysis tests → UAUTO workstation + Pot Urin container + // Urinalysis tests → UAUTO workstation $urineTests = ['UCOLOR', 'UGLUC', 'UPROT', 'PH']; foreach ($urineTests as $testCode) { $testMaps[] = [ @@ -290,14 +294,14 @@ class TestSeeder extends Seeder 'HostTestName' => $testCode, 'ClientType' => 'WORKSTATION', 'ClientID' => (string)$wsUAuto, - 'ConDefID' => $conPotUrin, + 'ConDefID' => null, 'ClientTestCode' => $testCode, 'ClientTestName' => $testCode, 'CreateDate' => $now ]; } - // Panel/Group tests - map to primary container (EDTA for CBC, SST for LIPID/LFT/RFT) + // Panel/Group tests $testMaps[] = [ 'TestSiteID' => $tIDs['CBC'], 'HostType' => 'SITE', @@ -306,7 +310,7 @@ class TestSeeder extends Seeder 'HostTestName' => 'CBC', 'ClientType' => 'WORKSTATION', 'ClientID' => (string)$wsHAuto, - 'ConDefID' => $conEDTA, + 'ConDefID' => null, 'ClientTestCode' => 'CBC', 'ClientTestName' => 'CBC', 'CreateDate' => $now @@ -320,7 +324,7 @@ class TestSeeder extends Seeder 'HostTestName' => 'LIPID', 'ClientType' => 'WORKSTATION', 'ClientID' => (string)$wsCAuto, - 'ConDefID' => $conSST, + 'ConDefID' => null, 'ClientTestCode' => 'LIPID', 'ClientTestName' => 'LIPID', 'CreateDate' => $now @@ -334,7 +338,7 @@ class TestSeeder extends Seeder 'HostTestName' => 'LFT', 'ClientType' => 'WORKSTATION', 'ClientID' => (string)$wsCAuto, - 'ConDefID' => $conSST, + 'ConDefID' => null, 'ClientTestCode' => 'LFT', 'ClientTestName' => 'LFT', 'CreateDate' => $now @@ -348,7 +352,7 @@ class TestSeeder extends Seeder 'HostTestName' => 'RFT', 'ClientType' => 'WORKSTATION', 'ClientID' => (string)$wsCAuto, - 'ConDefID' => $conSST, + 'ConDefID' => null, 'ClientTestCode' => 'RFT', 'ClientTestName' => 'RFT', 'CreateDate' => $now @@ -369,6 +373,141 @@ class TestSeeder extends Seeder 'CreateDate' => $now ]; + // ============================================ + // WORKSTATION → INSTRUMENT mappings (with ConDefID) + // ============================================ + + // Equipment IDs from OrganizationSeeder + $instHematology = 1; // Sysmex XN-1000 + $instChemistry = 2; // Roche Cobas C501 + $instImmunology = 3; // Architect i2000 + $instUrinalysis = 4; // Urisys 1100 + + // Hematology tests → HAUTO → Sysmex (EDTA container) + foreach ($hematologyTests as $testCode) { + $testMaps[] = [ + 'TestSiteID' => $tIDs[$testCode], + 'HostType' => 'WORKSTATION', + 'HostID' => (string)$wsHAuto, + 'HostTestCode' => $testCode, + 'HostTestName' => $testCode, + 'ClientType' => 'INSTRUMENT', + 'ClientID' => (string)$instHematology, + 'ConDefID' => $conEDTA, + 'ClientTestCode' => $testCode, + 'ClientTestName' => $testCode, + 'CreateDate' => $now + ]; + } + + // Chemistry tests → CAUTO → Cobas (SST container) + foreach ($chemistryTests as $testCode) { + $testMaps[] = [ + 'TestSiteID' => $tIDs[$testCode], + 'HostType' => 'WORKSTATION', + 'HostID' => (string)$wsCAuto, + 'HostTestCode' => $testCode, + 'HostTestName' => $testCode, + 'ClientType' => 'INSTRUMENT', + 'ClientID' => (string)$instChemistry, + 'ConDefID' => $conSST, + 'ClientTestCode' => $testCode, + 'ClientTestName' => $testCode, + 'CreateDate' => $now + ]; + } + + // Calculated chemistry tests → CAUTO → Cobas (SST container) + foreach ($calcChemistryTests as $testCode) { + $testMaps[] = [ + 'TestSiteID' => $tIDs[$testCode], + 'HostType' => 'WORKSTATION', + 'HostID' => (string)$wsCAuto, + 'HostTestCode' => $testCode, + 'HostTestName' => $testCode, + 'ClientType' => 'INSTRUMENT', + 'ClientID' => (string)$instChemistry, + 'ConDefID' => $conSST, + 'ClientTestCode' => $testCode, + 'ClientTestName' => $testCode, + 'CreateDate' => $now + ]; + } + + // Urinalysis tests → UAUTO → Urisys (Pot Urin container) + foreach ($urineTests as $testCode) { + $testMaps[] = [ + 'TestSiteID' => $tIDs[$testCode], + 'HostType' => 'WORKSTATION', + 'HostID' => (string)$wsUAuto, + 'HostTestCode' => $testCode, + 'HostTestName' => $testCode, + 'ClientType' => 'INSTRUMENT', + 'ClientID' => (string)$instUrinalysis, + 'ConDefID' => $conPotUrin, + 'ClientTestCode' => $testCode, + 'ClientTestName' => $testCode, + 'CreateDate' => $now + ]; + } + + // Panel/Group tests → Instruments + $testMaps[] = [ + 'TestSiteID' => $tIDs['CBC'], + 'HostType' => 'WORKSTATION', + 'HostID' => (string)$wsHAuto, + 'HostTestCode' => 'CBC', + 'HostTestName' => 'CBC', + 'ClientType' => 'INSTRUMENT', + 'ClientID' => (string)$instHematology, + 'ConDefID' => $conEDTA, + 'ClientTestCode' => 'CBC', + 'ClientTestName' => 'CBC', + 'CreateDate' => $now + ]; + + $testMaps[] = [ + 'TestSiteID' => $tIDs['LIPID'], + 'HostType' => 'WORKSTATION', + 'HostID' => (string)$wsCAuto, + 'HostTestCode' => 'LIPID', + 'HostTestName' => 'LIPID', + 'ClientType' => 'INSTRUMENT', + 'ClientID' => (string)$instChemistry, + 'ConDefID' => $conSST, + 'ClientTestCode' => 'LIPID', + 'ClientTestName' => 'LIPID', + 'CreateDate' => $now + ]; + + $testMaps[] = [ + 'TestSiteID' => $tIDs['LFT'], + 'HostType' => 'WORKSTATION', + 'HostID' => (string)$wsCAuto, + 'HostTestCode' => 'LFT', + 'HostTestName' => 'LFT', + 'ClientType' => 'INSTRUMENT', + 'ClientID' => (string)$instChemistry, + 'ConDefID' => $conSST, + 'ClientTestCode' => 'LFT', + 'ClientTestName' => 'LFT', + 'CreateDate' => $now + ]; + + $testMaps[] = [ + 'TestSiteID' => $tIDs['RFT'], + 'HostType' => 'WORKSTATION', + 'HostID' => (string)$wsCAuto, + 'HostTestCode' => 'RFT', + 'HostTestName' => 'RFT', + 'ClientType' => 'INSTRUMENT', + 'ClientID' => (string)$instChemistry, + 'ConDefID' => $conSST, + 'ClientTestCode' => 'RFT', + 'ClientTestName' => 'RFT', + 'CreateDate' => $now + ]; + $this->db->table('testmap')->insertBatch($testMaps); } } diff --git a/app/Models/Infrastructure/EquipmentListModel.php b/app/Models/Infrastructure/EquipmentListModel.php new file mode 100644 index 0000000..8e86b25 --- /dev/null +++ b/app/Models/Infrastructure/EquipmentListModel.php @@ -0,0 +1,65 @@ +builder(); + $builder->select('equipmentlist.*, department.DepartmentName, workstation.WorkstationName') + ->join('department', 'department.DepartmentID = equipmentlist.DepartmentID', 'left') + ->join('workstation', 'workstation.WorkstationID = equipmentlist.WorkstationID', 'left'); + + if (!empty($filter['IEID'])) { + $builder->like('equipmentlist.IEID', $filter['IEID'], 'both'); + } + if (!empty($filter['InstrumentName'])) { + $builder->like('equipmentlist.InstrumentName', $filter['InstrumentName'], 'both'); + } + if (!empty($filter['DepartmentID'])) { + $builder->where('equipmentlist.DepartmentID', $filter['DepartmentID']); + } + if (!empty($filter['WorkstationID'])) { + $builder->where('equipmentlist.WorkstationID', $filter['WorkstationID']); + } + if (isset($filter['Enable'])) { + $builder->where('equipmentlist.Enable', $filter['Enable']); + } + + $rows = $builder->get()->getResultArray(); + return $rows; + } + + public function getEquipmentList($EID) { + $builder = $this->builder(); + $row = $builder->select('equipmentlist.*, department.DepartmentName, workstation.WorkstationName') + ->join('department', 'department.DepartmentID = equipmentlist.DepartmentID', 'left') + ->join('workstation', 'workstation.WorkstationID = equipmentlist.WorkstationID', 'left') + ->where('equipmentlist.EID', $EID) + ->get() + ->getRowArray(); + + return $row; + } +} diff --git a/public/api-docs.bundled.yaml b/public/api-docs.bundled.yaml index 0c6a05b..098ec39 100644 --- a/public/api-docs.bundled.yaml +++ b/public/api-docs.bundled.yaml @@ -47,6 +47,8 @@ tags: description: Value set definitions and items - name: Demo description: Demo/test endpoints (no authentication) + - name: EquipmentList + description: Laboratory equipment and instrument management paths: /api/auth/login: post: @@ -594,6 +596,225 @@ paths: responses: '200': description: Status logged + /api/equipmentlist: + get: + tags: + - EquipmentList + summary: List equipment + description: Get list of equipment with optional filters + security: + - bearerAuth: [] + parameters: + - name: IEID + in: query + schema: + type: string + description: Filter by IEID + - name: InstrumentName + in: query + schema: + type: string + description: Filter by instrument name + - name: DepartmentID + in: query + schema: + type: integer + description: Filter by department ID + - name: WorkstationID + in: query + schema: + type: integer + description: Filter by workstation ID + - name: Enable + in: query + schema: + type: integer + enum: + - 0 + - 1 + description: Filter by enable status + responses: + '200': + description: List of equipment + content: + application/json: + schema: + type: object + properties: + status: + type: string + message: + type: string + data: + type: array + items: + $ref: '#/components/schemas/EquipmentList' + post: + tags: + - EquipmentList + summary: Create equipment + description: Create a new equipment entry + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - IEID + - DepartmentID + - Enable + - EquipmentRole + properties: + IEID: + type: string + maxLength: 50 + DepartmentID: + type: integer + InstrumentID: + type: string + maxLength: 150 + InstrumentName: + type: string + maxLength: 150 + WorkstationID: + type: integer + Enable: + type: integer + enum: + - 0 + - 1 + EquipmentRole: + type: string + maxLength: 1 + responses: + '201': + description: Equipment created + content: + application/json: + schema: + type: object + properties: + status: + type: string + message: + type: string + data: + type: integer + patch: + tags: + - EquipmentList + summary: Update equipment + description: Update an existing equipment entry + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - EID + properties: + EID: + type: integer + IEID: + type: string + maxLength: 50 + DepartmentID: + type: integer + InstrumentID: + type: string + maxLength: 150 + InstrumentName: + type: string + maxLength: 150 + WorkstationID: + type: integer + Enable: + type: integer + enum: + - 0 + - 1 + EquipmentRole: + type: string + maxLength: 1 + responses: + '200': + description: Equipment updated + content: + application/json: + schema: + type: object + properties: + status: + type: string + message: + type: string + data: + type: integer + delete: + tags: + - EquipmentList + summary: Delete equipment + description: Soft delete an equipment entry + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - EID + properties: + EID: + type: integer + responses: + '200': + description: Equipment deleted + content: + application/json: + schema: + type: object + properties: + status: + type: string + message: + type: string + /api/equipmentlist/{id}: + get: + tags: + - EquipmentList + summary: Get equipment by ID + description: Get a single equipment entry by its EID + security: + - bearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: integer + description: Equipment ID + responses: + '200': + description: Equipment details + content: + application/json: + schema: + type: object + properties: + status: + type: string + message: + type: string + data: + $ref: '#/components/schemas/EquipmentList' /api/location: get: tags: @@ -5163,6 +5384,55 @@ components: type: string format: date-time nullable: true + EquipmentList: + type: object + properties: + EID: + type: integer + description: Equipment ID (auto-increment) + IEID: + type: string + maxLength: 50 + description: Internal Equipment ID + DepartmentID: + type: integer + description: Reference to department + InstrumentID: + type: string + maxLength: 150 + description: Instrument identifier + InstrumentName: + type: string + maxLength: 150 + description: Instrument display name + WorkstationID: + type: integer + description: Reference to workstation + Enable: + type: integer + enum: + - 0 + - 1 + description: Equipment status (0=disabled, 1=enabled) + EquipmentRole: + type: string + maxLength: 1 + description: Equipment role code + CreateDate: + type: string + format: date-time + description: Creation timestamp + EndDate: + type: string + format: date-time + nullable: true + description: Deletion timestamp (soft delete) + DepartmentName: + type: string + description: Joined department name + WorkstationName: + type: string + description: Joined workstation name Contact: type: object properties: diff --git a/public/api-docs.yaml b/public/api-docs.yaml index f156941..73d64bf 100644 --- a/public/api-docs.yaml +++ b/public/api-docs.yaml @@ -49,6 +49,8 @@ tags: description: Value set definitions and items - name: Demo description: Demo/test endpoints (no authentication) + - name: EquipmentList + description: Laboratory equipment and instrument management components: securitySchemes: @@ -156,6 +158,10 @@ components: Location: $ref: './components/schemas/master-data.yaml#/Location' + # EquipmentList schemas + EquipmentList: + $ref: './components/schemas/equipmentlist.yaml#/EquipmentList' + # Paths are in separate files in the paths/ directory # To view the complete API with all paths, use: api-docs.bundled.yaml # To rebuild the bundle after changes: python bundle-api-docs.py diff --git a/public/components/schemas/equipmentlist.yaml b/public/components/schemas/equipmentlist.yaml new file mode 100644 index 0000000..795ebc2 --- /dev/null +++ b/public/components/schemas/equipmentlist.yaml @@ -0,0 +1,47 @@ +EquipmentList: + type: object + properties: + EID: + type: integer + description: Equipment ID (auto-increment) + IEID: + type: string + maxLength: 50 + description: Internal Equipment ID + DepartmentID: + type: integer + description: Reference to department + InstrumentID: + type: string + maxLength: 150 + description: Instrument identifier + InstrumentName: + type: string + maxLength: 150 + description: Instrument display name + WorkstationID: + type: integer + description: Reference to workstation + Enable: + type: integer + enum: [0, 1] + description: Equipment status (0=disabled, 1=enabled) + EquipmentRole: + type: string + maxLength: 1 + description: Equipment role code + CreateDate: + type: string + format: date-time + description: Creation timestamp + EndDate: + type: string + format: date-time + nullable: true + description: Deletion timestamp (soft delete) + DepartmentName: + type: string + description: Joined department name + WorkstationName: + type: string + description: Joined workstation name diff --git a/public/paths/equipmentlist.yaml b/public/paths/equipmentlist.yaml new file mode 100644 index 0000000..3194e9b --- /dev/null +++ b/public/paths/equipmentlist.yaml @@ -0,0 +1,212 @@ +/api/equipmentlist: + get: + tags: [EquipmentList] + summary: List equipment + description: Get list of equipment with optional filters + security: + - bearerAuth: [] + parameters: + - name: IEID + in: query + schema: + type: string + description: Filter by IEID + - name: InstrumentName + in: query + schema: + type: string + description: Filter by instrument name + - name: DepartmentID + in: query + schema: + type: integer + description: Filter by department ID + - name: WorkstationID + in: query + schema: + type: integer + description: Filter by workstation ID + - name: Enable + in: query + schema: + type: integer + enum: [0, 1] + description: Filter by enable status + responses: + '200': + description: List of equipment + content: + application/json: + schema: + type: object + properties: + status: + type: string + message: + type: string + data: + type: array + items: + $ref: '../components/schemas/equipmentlist.yaml#/EquipmentList' + + post: + tags: [EquipmentList] + summary: Create equipment + description: Create a new equipment entry + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - IEID + - DepartmentID + - Enable + - EquipmentRole + properties: + IEID: + type: string + maxLength: 50 + DepartmentID: + type: integer + InstrumentID: + type: string + maxLength: 150 + InstrumentName: + type: string + maxLength: 150 + WorkstationID: + type: integer + Enable: + type: integer + enum: [0, 1] + EquipmentRole: + type: string + maxLength: 1 + responses: + '201': + description: Equipment created + content: + application/json: + schema: + type: object + properties: + status: + type: string + message: + type: string + data: + type: integer + + patch: + tags: [EquipmentList] + summary: Update equipment + description: Update an existing equipment entry + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - EID + properties: + EID: + type: integer + IEID: + type: string + maxLength: 50 + DepartmentID: + type: integer + InstrumentID: + type: string + maxLength: 150 + InstrumentName: + type: string + maxLength: 150 + WorkstationID: + type: integer + Enable: + type: integer + enum: [0, 1] + EquipmentRole: + type: string + maxLength: 1 + responses: + '200': + description: Equipment updated + content: + application/json: + schema: + type: object + properties: + status: + type: string + message: + type: string + data: + type: integer + + delete: + tags: [EquipmentList] + summary: Delete equipment + description: Soft delete an equipment entry + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - EID + properties: + EID: + type: integer + responses: + '200': + description: Equipment deleted + content: + application/json: + schema: + type: object + properties: + status: + type: string + message: + type: string + +/api/equipmentlist/{id}: + get: + tags: [EquipmentList] + summary: Get equipment by ID + description: Get a single equipment entry by its EID + security: + - bearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: integer + description: Equipment ID + responses: + '200': + description: Equipment details + content: + application/json: + schema: + type: object + properties: + status: + type: string + message: + type: string + data: + $ref: '../components/schemas/equipmentlist.yaml#/EquipmentList'