remove actions and data integrity
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* ContactManager
|
||||
*
|
||||
* Encapsulates contact and contact type operations:
|
||||
* - Soft-delete with downstream validation
|
||||
*
|
||||
* Note: Methods that modify data do NOT manage their own DB transactions.
|
||||
* Callers are responsible for wrapping operations in dbTransaction() when atomicity is needed.
|
||||
*/
|
||||
class ContactManager {
|
||||
|
||||
private $pdo;
|
||||
private $company_id;
|
||||
|
||||
public function __construct($pdo, $company_id) {
|
||||
$this->pdo = $pdo;
|
||||
$this->company_id = $company_id;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Private helpers
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Check if a contact_id is referenced in any active td_stock_* row.
|
||||
* Returns true if blocking stock exists.
|
||||
*/
|
||||
private function hasActiveStock(int $contact_id): bool {
|
||||
|
||||
$sth = $this->pdo->prepare(
|
||||
"SELECT table_name FROM information_schema.tables
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name LIKE 'td_stock_%'"
|
||||
);
|
||||
$sth->execute();
|
||||
$tables = $sth->fetchAll(PDO::FETCH_COLUMN);
|
||||
|
||||
foreach ($tables as $table) {
|
||||
$sth = $this->pdo->prepare(
|
||||
"SELECT COUNT(*) FROM `$table`
|
||||
WHERE company_id = :company_id
|
||||
AND contact_id = :contact_id"
|
||||
);
|
||||
$sth->execute([
|
||||
':company_id' => $this->company_id,
|
||||
':contact_id' => $contact_id,
|
||||
]);
|
||||
if ($sth->fetchColumn() > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a log entry array for audit context.
|
||||
*/
|
||||
private function buildLogEntry(string $action): array {
|
||||
return [
|
||||
'user_id' => $_SESSION['login_user_id'] ?? null,
|
||||
'dt' => date('Y-m-d H:i:s'),
|
||||
'login' => isset($_SESSION['otpTime'])
|
||||
? date('Y-m-d H:i:s', $_SESSION['otpTime'])
|
||||
: null,
|
||||
'action' => $action,
|
||||
];
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Contact type
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Soft-delete a contact type.
|
||||
*
|
||||
* Blocks if any md_contact references this type.
|
||||
*
|
||||
* @throws Exception if contact type not found or has dependent contacts
|
||||
*/
|
||||
public function deleteContactType(int $type_id): void {
|
||||
|
||||
$sth = $this->pdo->prepare(
|
||||
"SELECT id, contact_type, `log` FROM md_contact_type
|
||||
WHERE company_id = :company_id AND id = :id"
|
||||
);
|
||||
$sth->execute([
|
||||
':company_id' => $this->company_id,
|
||||
':id' => $type_id,
|
||||
]);
|
||||
$row = $sth->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$row) {
|
||||
throw new Exception("Contact type not found.");
|
||||
}
|
||||
|
||||
// Block if any contact references this type
|
||||
$sth = $this->pdo->prepare(
|
||||
"SELECT COUNT(*) FROM md_contact
|
||||
WHERE company_id = :company_id
|
||||
AND contact_type = :type_id"
|
||||
);
|
||||
$sth->execute([
|
||||
':company_id' => $this->company_id,
|
||||
':type_id' => $type_id,
|
||||
]);
|
||||
|
||||
if ($sth->fetchColumn() > 0) {
|
||||
throw new Exception(
|
||||
"Cannot delete — contact type \"{$row['contact_type']}\" " .
|
||||
"still has contacts assigned to it."
|
||||
);
|
||||
}
|
||||
|
||||
// Append delete event to log
|
||||
$log = json_decode($row['log'] ?? '[]', true) ?: [];
|
||||
$log[] = $this->buildLogEntry('delete');
|
||||
|
||||
// Soft-delete
|
||||
$this->pdo->prepare(
|
||||
"UPDATE md_contact_type
|
||||
SET company_id = company_id * -1,
|
||||
`log` = :log
|
||||
WHERE id = :id AND company_id = :company_id"
|
||||
)->execute([
|
||||
':log' => json_encode($log),
|
||||
':id' => $type_id,
|
||||
':company_id' => $this->company_id,
|
||||
]);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Contact
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Soft-delete a contact.
|
||||
*
|
||||
* Blocks if the contact is referenced in any active td_stock_* row.
|
||||
*
|
||||
* @throws Exception if contact not found or referenced in active stock
|
||||
*/
|
||||
public function deleteContact(int $contact_id): void {
|
||||
|
||||
$sth = $this->pdo->prepare(
|
||||
"SELECT id, contact_name, `log` FROM md_contact
|
||||
WHERE company_id = :company_id AND id = :id"
|
||||
);
|
||||
$sth->execute([
|
||||
':company_id' => $this->company_id,
|
||||
':id' => $contact_id,
|
||||
]);
|
||||
$row = $sth->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$row) {
|
||||
throw new Exception("Contact not found.");
|
||||
}
|
||||
|
||||
// Block if contact is referenced in any active stock transaction
|
||||
if ($this->hasActiveStock($contact_id)) {
|
||||
throw new Exception(
|
||||
"Cannot delete — \"{$row['contact_name']}\" " .
|
||||
"is referenced in active stock transactions."
|
||||
);
|
||||
}
|
||||
|
||||
// Append delete event to log
|
||||
$log = json_decode($row['log'] ?? '[]', true) ?: [];
|
||||
$log[] = $this->buildLogEntry('delete');
|
||||
|
||||
// Soft-delete
|
||||
$this->pdo->prepare(
|
||||
"UPDATE md_contact
|
||||
SET company_id = company_id * -1,
|
||||
`log` = :log
|
||||
WHERE id = :id AND company_id = :company_id"
|
||||
)->execute([
|
||||
':log' => json_encode($log),
|
||||
':id' => $contact_id,
|
||||
':company_id' => $this->company_id,
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user