Surface specific validation and database failures when updating contact details so API responses are actionable.
168 lines
4.7 KiB
PHP
Executable File
168 lines
4.7 KiB
PHP
Executable File
<?php
|
|
|
|
namespace App\Models\Contact;
|
|
|
|
use App\Models\BaseModel;
|
|
|
|
class ContactDetailModel extends BaseModel {
|
|
protected $table = 'contactdetail';
|
|
protected $primaryKey = 'ContactDetID';
|
|
protected $allowedFields = ['ContactID', 'SiteID', 'ContactCode', 'ContactEmail', 'OccupationID', 'JobTitle', 'Department', 'ContactStartDate', 'ContactEndDate'];
|
|
|
|
protected $useTimestamps = true;
|
|
protected $createdField = 'ContactStartDate';
|
|
protected $updatedField = '';
|
|
protected $useSoftDeletes = true;
|
|
protected $deletedField = 'ContactEndDate';
|
|
|
|
public function syncDetails(int $ContactID, array $contactDetails) {
|
|
try {
|
|
$keptSiteIDs = [];
|
|
|
|
foreach ($contactDetails as $detail) {
|
|
if (empty($detail['SiteID'])) {
|
|
continue;
|
|
}
|
|
|
|
$detail['ContactID'] = $ContactID;
|
|
|
|
$existing = $this->where('ContactID', $ContactID)
|
|
->where('SiteID', $detail['SiteID'])
|
|
->first();
|
|
|
|
if ($existing) {
|
|
$this->update($existing[$this->primaryKey], $detail);
|
|
} else {
|
|
$this->insert($detail);
|
|
}
|
|
|
|
$keptSiteIDs[] = $detail['SiteID'];
|
|
}
|
|
|
|
// Delete missing rows
|
|
if (!empty($keptSiteIDs)) {
|
|
$this->where('ContactID', $ContactID)
|
|
->whereNotIn('SiteID', $keptSiteIDs)
|
|
->delete();
|
|
} else {
|
|
$this->where('ContactID', $ContactID)->delete();
|
|
}
|
|
|
|
return [
|
|
'status' => 'success',
|
|
'inserted' => count($contactDetails) - count($keptSiteIDs),
|
|
'kept' => count($keptSiteIDs),
|
|
];
|
|
} catch (\Throwable $e) {
|
|
log_message('error', 'syncDetails error: ' . $e->getMessage());
|
|
|
|
return [
|
|
'status' => 'error',
|
|
'message' => $e->getMessage(),
|
|
];
|
|
}
|
|
}
|
|
|
|
public function applyDetailOperations(int $contactID, array $operations): array
|
|
{
|
|
try {
|
|
if (!empty($operations['edited']) && !$this->updateDetails($contactID, $operations['edited'])) {
|
|
return ['status' => 'error', 'message' => $this->lastDetailError ?? 'Failed to update contact details'];
|
|
}
|
|
|
|
if (!empty($operations['deleted']) && !$this->softDeleteDetails($contactID, $operations['deleted'])) {
|
|
return ['status' => 'error', 'message' => 'Failed to delete contact details'];
|
|
}
|
|
|
|
if (!empty($operations['created']) && !$this->insertDetailRows($contactID, $operations['created'])) {
|
|
return ['status' => 'error', 'message' => 'Failed to create contact details'];
|
|
}
|
|
|
|
return ['status' => 'success'];
|
|
} catch (\Throwable $e) {
|
|
log_message('error', 'applyDetailOperations error: ' . $e->getMessage());
|
|
|
|
return [
|
|
'status' => 'error',
|
|
'message' => $e->getMessage(),
|
|
];
|
|
}
|
|
}
|
|
|
|
private function insertDetailRows(int $contactID, array $items): bool
|
|
{
|
|
foreach ($items as $item) {
|
|
if (!is_array($item)) {
|
|
return false;
|
|
}
|
|
|
|
$item['ContactID'] = $contactID;
|
|
$this->insert($item);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private function updateDetails(int $contactID, array $items): bool
|
|
{
|
|
foreach ($items as $detail) {
|
|
$detailID = $detail['ContactDetID'] ?? null;
|
|
if (!$detailID || !ctype_digit((string) $detailID)) {
|
|
$this->lastDetailError = 'ContactDetID is required and must be an integer.';
|
|
return false;
|
|
}
|
|
|
|
$existing = $this->where('ContactDetID', (int) $detailID)
|
|
->where('ContactID', $contactID)
|
|
->where('ContactEndDate', null)
|
|
->first();
|
|
|
|
if (empty($existing)) {
|
|
$this->lastDetailError = "Detail record {$detailID} not found for Contact {$contactID}.";
|
|
return false;
|
|
}
|
|
|
|
$updateData = array_intersect_key($detail, array_flip($this->allowedFields));
|
|
unset($updateData['ContactID']);
|
|
|
|
if ($updateData !== [] && !$this->update((int) $detailID, $updateData)) {
|
|
$dbError = $this->db->error();
|
|
$this->lastDetailError = sprintf(
|
|
'Failed to update detail %d for Contact %d%s',
|
|
(int) $detailID,
|
|
$contactID,
|
|
!empty($dbError['message']) ? ': ' . $dbError['message'] : ''
|
|
);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private ?string $lastDetailError = null;
|
|
|
|
private function softDeleteDetails(int $contactID, array $ids): bool
|
|
{
|
|
$ids = array_values(array_unique(array_map('intval', $ids)));
|
|
if ($ids === []) {
|
|
return true;
|
|
}
|
|
|
|
$existing = $this->select('ContactDetID')
|
|
->whereIn('ContactDetID', $ids)
|
|
->where('ContactID', $contactID)
|
|
->findAll();
|
|
|
|
if (count($existing) !== count($ids)) {
|
|
return false;
|
|
}
|
|
|
|
$this->whereIn('ContactDetID', $ids)
|
|
->where('ContactID', $contactID)
|
|
->delete();
|
|
|
|
return true;
|
|
}
|
|
}
|