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, ]); } }