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; $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

@ -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);
} }
} }

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": [

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

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