373 lines
14 KiB
PHP
373 lines
14 KiB
PHP
<?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,
|
|
]);
|
|
}
|
|
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
// Contact type queries
|
|
// ─────────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Full contact type list.
|
|
*/
|
|
public function getContactTypeList(): array
|
|
{
|
|
$sth = $this->pdo->prepare(
|
|
"SELECT * FROM md_contact_type
|
|
WHERE company_id = :company_id"
|
|
);
|
|
$sth->execute([':company_id' => $this->company_id]);
|
|
return $sth->fetchAll(PDO::FETCH_ASSOC);
|
|
}
|
|
|
|
/**
|
|
* Fetch a single contact type row by ID.
|
|
*/
|
|
public function getContactTypeById(int $id): array|false
|
|
{
|
|
$sth = $this->pdo->prepare(
|
|
"SELECT * FROM md_contact_type
|
|
WHERE company_id = :company_id AND id = :id"
|
|
);
|
|
$sth->execute([':company_id' => $this->company_id, ':id' => $id]);
|
|
return $sth->fetch(PDO::FETCH_ASSOC);
|
|
}
|
|
|
|
/**
|
|
* Insert or update a contact type.
|
|
* Pass $data['id'] > 0 for update, 0 for insert.
|
|
* Must be called inside dbTransaction() by the caller.
|
|
*/
|
|
public function saveContactType(array $data, array $logging): void
|
|
{
|
|
$id = (int)($data['id'] ?? 0);
|
|
|
|
$sth = $this->pdo->prepare(
|
|
"SELECT `log` FROM md_contact_type
|
|
WHERE company_id = :company_id AND id = :id"
|
|
);
|
|
$sth->execute([':company_id' => $this->company_id, ':id' => $id]);
|
|
$table_log = json_decode($sth->fetchColumn() ?: '[]', true) ?: [];
|
|
$table_log[] = $logging;
|
|
|
|
$params = [
|
|
':company_id' => $this->company_id,
|
|
':contact_type' => $data['contact_type'],
|
|
':description' => $data['description'],
|
|
':status' => (int)$data['status'],
|
|
':log' => json_encode($table_log),
|
|
];
|
|
|
|
if ($id > 0) {
|
|
$params[':id'] = $id;
|
|
$this->pdo->prepare(
|
|
"UPDATE md_contact_type SET
|
|
`contact_type` = :contact_type,
|
|
`description` = :description,
|
|
`status` = :status,
|
|
`log` = :log
|
|
WHERE id = :id AND company_id = :company_id"
|
|
)->execute($params);
|
|
} else {
|
|
$this->pdo->prepare(
|
|
"INSERT INTO md_contact_type
|
|
(company_id, contact_type, `description`, `status`, `log`)
|
|
VALUES
|
|
(:company_id, :contact_type, :description, :status, :log)"
|
|
)->execute($params);
|
|
}
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
// Contact queries
|
|
// ─────────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Full contact list.
|
|
*/
|
|
public function getContactList(): array
|
|
{
|
|
$sth = $this->pdo->prepare(
|
|
"SELECT * FROM md_contact
|
|
WHERE company_id = :company_id"
|
|
);
|
|
$sth->execute([':company_id' => $this->company_id]);
|
|
return $sth->fetchAll(PDO::FETCH_ASSOC);
|
|
}
|
|
|
|
/**
|
|
* Fetch a single contact row by ID.
|
|
*/
|
|
public function getContactById(int $id): array|false
|
|
{
|
|
$sth = $this->pdo->prepare(
|
|
"SELECT * FROM md_contact
|
|
WHERE company_id = :company_id AND id = :id"
|
|
);
|
|
$sth->execute([':company_id' => $this->company_id, ':id' => $id]);
|
|
return $sth->fetch(PDO::FETCH_ASSOC);
|
|
}
|
|
|
|
/**
|
|
* Search contacts by name keyword — for autocomplete.
|
|
*/
|
|
public function searchContact(string $keyword): array
|
|
{
|
|
$sth = $this->pdo->prepare(
|
|
"SELECT * FROM md_contact
|
|
WHERE company_id = :company_id
|
|
AND contact_name LIKE :keyword
|
|
LIMIT 50"
|
|
);
|
|
$sth->execute([
|
|
':company_id' => $this->company_id,
|
|
':keyword' => '%' . $keyword . '%',
|
|
]);
|
|
return $sth->fetchAll(PDO::FETCH_ASSOC);
|
|
}
|
|
|
|
|
|
/**
|
|
* Insert or update a contact.
|
|
* Pass $data['id'] > 0 for update, 0 for insert.
|
|
* Must be called inside dbTransaction() by the caller.
|
|
*/
|
|
public function saveContact(array $data, array $logging, string $contact_image): void
|
|
{
|
|
$id = (int)($data['id'] ?? 0);
|
|
|
|
$sth = $this->pdo->prepare(
|
|
"SELECT `log` FROM md_contact
|
|
WHERE company_id = :company_id AND id = :id"
|
|
);
|
|
$sth->execute([':company_id' => $this->company_id, ':id' => $id]);
|
|
$table_log = json_decode($sth->fetchColumn() ?: '[]', true) ?: [];
|
|
$table_log[] = $logging;
|
|
|
|
$params = [
|
|
':company_id' => $this->company_id,
|
|
':contact_name' => $data['contact_name'],
|
|
':tax_id' => $data['tax_id'],
|
|
':organization' => $data['organization'],
|
|
':branch' => $data['branch'],
|
|
':contact_type' => (int)$data['contact_type'],
|
|
':billing_address' => $data['billing_address'],
|
|
':shipping_location' => $data['shipping_location'],
|
|
':shipping_address' => $data['shipping_address'],
|
|
':remark' => $data['remark'],
|
|
':contact_image' => $contact_image,
|
|
':status' => (int)$data['status'],
|
|
':log' => json_encode($table_log),
|
|
];
|
|
|
|
if ($id > 0) {
|
|
$params[':id'] = $id;
|
|
$this->pdo->prepare(
|
|
"UPDATE md_contact SET
|
|
`contact_name` = :contact_name,
|
|
`tax_id` = :tax_id,
|
|
`organization` = :organization,
|
|
`branch` = :branch,
|
|
`contact_type` = :contact_type,
|
|
`billing_address` = :billing_address,
|
|
`shipping_location` = :shipping_location,
|
|
`shipping_address` = :shipping_address,
|
|
`remark` = :remark,
|
|
`contact_image` = :contact_image,
|
|
`status` = :status,
|
|
`log` = :log
|
|
WHERE id = :id AND company_id = :company_id"
|
|
)->execute($params);
|
|
} else {
|
|
$this->pdo->prepare(
|
|
"INSERT INTO md_contact
|
|
(company_id, contact_name, tax_id, organization, branch, contact_type,
|
|
billing_address, shipping_location, shipping_address, remark, contact_image, status, log)
|
|
VALUES
|
|
(:company_id, :contact_name, :tax_id, :organization, :branch, :contact_type,
|
|
:billing_address, :shipping_location, :shipping_address, :remark, :contact_image, :status, :log)"
|
|
)->execute($params);
|
|
}
|
|
}
|
|
} |