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

View File

@ -115,9 +115,17 @@ class ContactController extends BaseController {
$input['ContactID'] = $id;
try {
$this->model->saveContact($input);
$id = $input['ContactID'];
return $this->respond([ 'status' => 'success', 'message' => 'data updated successfully', 'data' => $id ], 200);
$result = $this->model->saveContact($input);
if (($result['status'] ?? 'error') !== 'success') {
return $this->respond([
'status' => 'failed',
'message' => $result['message'] ?? 'Failed to update contact',
'data' => []
], 400);
}
return $this->respond([ 'status' => 'success', 'message' => 'data updated successfully', 'data' => $result ], 200);
} catch (\Throwable $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
}

View File

@ -113,6 +113,7 @@ class ContactDetailModel extends BaseModel {
$existing = $this->where('ContactDetID', (int) $detailID)
->where('ContactID', $contactID)
->where('ContactEndDate', null)
->first();
if (empty($existing)) {
@ -122,13 +123,8 @@ class ContactDetailModel extends BaseModel {
$updateData = array_intersect_key($detail, array_flip($this->allowedFields));
unset($updateData['ContactID']);
if ($updateData !== []) {
$db = \Config\Database::connect();
$db->table($this->table)
->where('ContactDetID', (int) $detailID)
->where('ContactID', $contactID)
->where('ContactEndDate', null)
->update($updateData);
if ($updateData !== [] && !$this->update((int) $detailID, $updateData)) {
return false;
}
}

69
composer.lock generated
View File

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

View File

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

View File

@ -347,7 +347,7 @@ class TestCreateVariantsTest extends CIUnitTestCase
$response = $this->withBodyFormat('json')->call('post', $this->endpoint, $payload);
$response->assertStatus(201);
$response->assertJSONFragment([
'status' => 'created',
'status' => 'success',
'message' => 'Test created successfully',
]);
@ -390,7 +390,7 @@ class TestCreateVariantsTest extends CIUnitTestCase
$response = $this->withBodyFormat('json')->call('post', $this->endpoint, $payload);
$response->assertStatus(201);
$response->assertJSONFragment([
'status' => 'created',
'status' => 'success',
'message' => 'Test created successfully',
]);
@ -419,7 +419,7 @@ class TestCreateVariantsTest extends CIUnitTestCase
$response = $this->withBodyFormat('json')->call('post', $this->endpoint, $payload);
$response->assertStatus(201);
$response->assertJSONFragment([
'status' => 'created',
'status' => 'success',
'message' => 'Test created successfully',
]);