Files
wms-app/app/assets/utils/classes_ac/ChartOfAccounts.php
T
Thanakorn SandClaude Sonnet 4.6 dfeb57533a Master data review: spec updates and C1/C2/M3-M6 fixes
Spec (docs/reviewing/master-data.md):
- M1: Remove margin from stored product fields (it's a derived frontend value)
- M2: Clarify posting window only restricts transaction dates, not master data
- M5: Document AccountFormulaManager::delete() as archive, not physical delete

Code:
- C1: Fix CompanySettingManager property typo (company_id → companyId) —
  prevented PHP 8.4 dynamic property fatal on all posting window operations
- C2: Fix WarehouseManager::deleteWarehouse() guard — was comparing
  warehouse_name (string) against warehouse id column (no-op); now correctly
  blocks on storage rows and active stock rows
- M3: Remove hard-delete of td_rack_log in deleteStorage() — retain rack
  history consistent with soft-delete philosophy elsewhere
- M4: Add reference guards to ChartOfAccounts::delete() (blocks on GL items,
  formula items, product account mappings) and DepartmentManager::delete()
  (blocks on GL items)
- M6: Fix CompanySettingManager::handle() partial update — only upsert keys
  present in the request, not all allowed keys defaulted

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 17:33:58 +07:00

191 lines
6.9 KiB
PHP

<?php
class ChartOfAccounts
{
private PDO $pdo;
private int $companyId;
public function __construct(PDO $pdo, int $company_id)
{
$this->pdo = $pdo;
$this->companyId = $company_id;
}
public function getAll(): array
{
$sth = $this->pdo->prepare(
"SELECT * FROM md_account
WHERE company_id = :cid
ORDER BY account_code ASC"
);
$sth->execute([':cid' => $this->companyId]);
return $sth->fetchAll(PDO::FETCH_ASSOC);
}
public function getByCode(string $account_code): ?array
{
$sth = $this->pdo->prepare(
"SELECT * FROM md_account
WHERE company_id = :cid AND account_code = :code
LIMIT 1"
);
$sth->execute([':cid' => $this->companyId, ':code' => $account_code]);
$row = $sth->fetch(PDO::FETCH_ASSOC);
return $row ?: null;
}
public function getByType(string $account_type): array
{
$sth = $this->pdo->prepare(
"SELECT * FROM md_account
WHERE company_id = :cid AND account_type = :type AND status = 1
ORDER BY account_code ASC"
);
$sth->execute([':cid' => $this->companyId, ':type' => $account_type]);
return $sth->fetchAll(PDO::FETCH_ASSOC);
}
public function getById(int $id): ?array
{
$sth = $this->pdo->prepare(
"SELECT * FROM md_account
WHERE company_id = :cid AND id = :id
LIMIT 1"
);
$sth->execute([':cid' => $this->companyId, ':id' => $id]);
$row = $sth->fetch(PDO::FETCH_ASSOC);
return $row ?: null;
}
public function create(array $data): int
{
$sth = $this->pdo->prepare(
"INSERT INTO md_account
(company_id, account_code, account_name, account_type, account_category, parent_code, is_posting, status)
VALUES
(:cid, :code, :name, :type, :category, :parent, :posting, :status)"
);
$sth->execute([
':cid' => $this->companyId,
':code' => trim($data['account_code']),
':name' => trim($data['account_name']),
':type' => $data['account_type'],
':category' => !empty($data['account_category']) ? trim($data['account_category']) : '',
':parent' => !empty($data['parent_code']) ? trim($data['parent_code']) : null,
':posting' => (int)($data['is_posting'] ?? 1),
':status' => (int)($data['status'] ?? 1),
]);
return (int)$this->pdo->lastInsertId();
}
public function update(int $id, array $data): void
{
$sth = $this->pdo->prepare(
"UPDATE md_account SET
account_code = :code,
account_name = :name,
account_type = :type,
account_category = :category,
parent_code = :parent,
is_posting = :posting,
status = :status
WHERE company_id = :cid AND id = :id"
);
$sth->execute([
':cid' => $this->companyId,
':id' => $id,
':code' => trim($data['account_code']),
':name' => trim($data['account_name']),
':type' => $data['account_type'],
':category' => !empty($data['account_category']) ? trim($data['account_category']) : '',
':parent' => !empty($data['parent_code']) ? trim($data['parent_code']) : null,
':posting' => (int)($data['is_posting'] ?? 1),
':status' => (int)($data['status'] ?? 1),
]);
}
public function delete(int $id): void
{
$sth = $this->pdo->prepare(
"SELECT account_code FROM md_account
WHERE company_id = :cid AND id = :id LIMIT 1"
);
$sth->execute([':cid' => $this->companyId, ':id' => $id]);
$row = $sth->fetch(PDO::FETCH_ASSOC);
if (!$row) throw new Exception('Account not found.');
$code = $row['account_code'];
$sth = $this->pdo->prepare(
"SELECT COUNT(*) FROM td_gl_item WHERE account_code = :code LIMIT 1"
);
$sth->execute([':code' => $code]);
if ((int)$sth->fetchColumn() > 0) {
throw new Exception("Cannot deactivate — account {$code} has GL journal entries.");
}
$sth = $this->pdo->prepare(
"SELECT COUNT(*) FROM md_account_formula_item
WHERE company_id = :cid AND account_code = :code LIMIT 1"
);
$sth->execute([':cid' => $this->companyId, ':code' => $code]);
if ((int)$sth->fetchColumn() > 0) {
throw new Exception("Cannot deactivate — account {$code} is used in one or more account formulas.");
}
$sth = $this->pdo->prepare(
"SELECT COUNT(*) FROM md_product
WHERE company_id = :cid
AND (sales_account_code = :code OR purchase_account_code = :code) LIMIT 1"
);
$sth->execute([':cid' => $this->companyId, ':code' => $code]);
if ((int)$sth->fetchColumn() > 0) {
throw new Exception("Cannot deactivate — account {$code} is mapped to one or more products.");
}
$this->pdo->prepare(
"UPDATE md_account SET status = 0
WHERE company_id = :cid AND id = :id"
)->execute([':cid' => $this->companyId, ':id' => $id]);
}
public function isPostingAccount(string $account_code): bool
{
$sth = $this->pdo->prepare(
"SELECT is_posting FROM md_account
WHERE company_id = :cid AND account_code = :code AND status = 1
LIMIT 1"
);
$sth->execute([':cid' => $this->companyId, ':code' => $account_code]);
return (bool)$sth->fetchColumn();
}
public function getStats(): array
{
$sth = $this->pdo->prepare(
"SELECT
COUNT(*) AS total,
SUM(status = 1) AS active,
SUM(status = 0) AS inactive,
SUM(account_type = 'asset') AS asset,
SUM(account_type = 'liability') AS liability,
SUM(account_type = 'equity') AS equity,
SUM(account_type = 'revenue') AS revenue,
SUM(account_type = 'expense') AS expense
FROM md_account
WHERE company_id = :cid"
);
$sth->execute([':cid' => $this->companyId]);
return $sth->fetch(PDO::FETCH_ASSOC) ?: [];
}
public function getByCategory(string $category): array
{
$sth = $this->pdo->prepare(
"SELECT * FROM md_account
WHERE company_id = :cid AND account_category = :cat AND status = 1
ORDER BY account_code ASC"
);
$sth->execute([':cid' => $this->companyId, ':cat' => $category]);
return $sth->fetchAll(PDO::FETCH_ASSOC);
}
}