379 lines
16 KiB
PHP
379 lines
16 KiB
PHP
<?php
|
|
class GlQueryManager
|
|
{
|
|
private PDO $pdo;
|
|
private int $companyId;
|
|
|
|
private array $invoiceTypes = ['invoice', 'credit_note', 'purchase_invoice', 'supplier_credit_note'];
|
|
private array $mappingTypes = ['invoice', 'credit_note', 'purchase_invoice', 'supplier_credit_note', 'purchase_order'];
|
|
private array $postingTypes = ['invoice', 'credit_note', 'purchase_invoice', 'supplier_credit_note', 'receipt', 'payment', 'purchase_order'];
|
|
private array $journalTypes = ['invoice', 'credit_note', 'purchase_invoice', 'supplier_credit_note', 'receipt', 'payment', 'manual', 'purchase_order'];
|
|
|
|
public function __construct(PDO $pdo, int $company_id)
|
|
{
|
|
$this->pdo = $pdo;
|
|
$this->companyId = $company_id;
|
|
}
|
|
|
|
public function getPostableDocuments(string $doc_type, string $date_from = '', string $date_to = '', int $formula_id = 0): array
|
|
{
|
|
$doc_type = trim($doc_type);
|
|
$date_from = $this->parseDate($date_from);
|
|
$date_to = $this->parseDate($date_to);
|
|
|
|
if (!in_array($doc_type, $this->postingTypes, true)) {
|
|
throw new Exception('Invalid doc_type.');
|
|
}
|
|
|
|
if (in_array($doc_type, $this->invoiceTypes, true)) {
|
|
$rows = $this->getInvoicePostableDocuments($doc_type, $date_from, $date_to, $formula_id);
|
|
} elseif ($doc_type === 'receipt') {
|
|
$rows = $this->getReceiptPostableDocuments($date_from, $date_to);
|
|
} elseif ($doc_type === 'purchase_order') {
|
|
$rows = $this->getPurchaseOrderPostableDocuments($date_from, $date_to, $formula_id);
|
|
} else {
|
|
$rows = $this->getPaymentPostableDocuments($date_from, $date_to);
|
|
}
|
|
|
|
if ($formula_id > 0) {
|
|
$rows = array_values(array_filter($rows, function($row) use ($formula_id) {
|
|
return $row['gl_status'] == 0 || (int)$row['gl_formula_id'] === $formula_id;
|
|
}));
|
|
}
|
|
|
|
return $rows;
|
|
}
|
|
|
|
public function getJournalListing(string $source_type = '', string $date_from = '', string $date_to = ''): array
|
|
{
|
|
$source_type = trim($source_type);
|
|
$date_from = $this->parseDate($date_from);
|
|
$date_to = $this->parseDate($date_to);
|
|
|
|
$where = ['g.company_id = :cid'];
|
|
$params = [':cid' => $this->companyId];
|
|
|
|
if ($source_type && in_array($source_type, $this->journalTypes, true)) {
|
|
$where[] = 'g.source_type = :source_type';
|
|
$params[':source_type'] = $source_type;
|
|
}
|
|
if ($date_from) {
|
|
$where[] = 'g.journal_date >= :date_from';
|
|
$params[':date_from'] = $date_from;
|
|
}
|
|
if ($date_to) {
|
|
$where[] = 'g.journal_date <= :date_to';
|
|
$params[':date_to'] = $date_to;
|
|
}
|
|
|
|
$sql = "
|
|
SELECT
|
|
g.id,
|
|
g.source_type,
|
|
g.source_id,
|
|
g.period,
|
|
g.current_version,
|
|
g.formula_id,
|
|
COALESCE(f.formula_name, '') AS formula_name,
|
|
DATE_FORMAT(g.created_at, '%d/%m/%Y %H:%i') AS posted_at,
|
|
DATE_FORMAT(g.updated_at, '%d/%m/%Y %H:%i') AS updated_at,
|
|
CASE g.source_type
|
|
WHEN 'receipt' THEN r.receipt_number
|
|
WHEN 'payment' THEN p.payment_number
|
|
WHEN 'manual' THEN IF(g.reference != '', g.reference, CONCAT('MJE-', g.id))
|
|
WHEN 'purchase_order' THEN po.po_number
|
|
ELSE i.invoice_number
|
|
END AS doc_number,
|
|
COALESCE(
|
|
CASE g.source_type
|
|
WHEN 'receipt' THEN cr.contact_name
|
|
WHEN 'payment' THEN cp.contact_name
|
|
WHEN 'manual' THEN g.description
|
|
WHEN 'purchase_order' THEN cpo.contact_name
|
|
ELSE ci.contact_name
|
|
END, ''
|
|
) AS contact_name,
|
|
COALESCE(SUM(gi.debit), 0) AS total_debit,
|
|
COALESCE(SUM(gi.credit), 0) AS total_credit
|
|
FROM td_gl g
|
|
LEFT JOIN md_account_formula f
|
|
ON f.company_id = g.company_id AND f.id = g.formula_id
|
|
LEFT JOIN td_gl_item gi
|
|
ON gi.company_id = g.company_id AND gi.gl_id = g.id
|
|
LEFT JOIN td_invoice i
|
|
ON g.source_type NOT IN ('receipt','payment','manual','purchase_order')
|
|
AND i.company_id = g.company_id AND i.id = g.source_id
|
|
LEFT JOIN md_contact ci
|
|
ON ci.company_id = g.company_id AND ci.id = i.contact_id
|
|
LEFT JOIN td_receipt r
|
|
ON g.source_type = 'receipt'
|
|
AND r.company_id = g.company_id AND r.id = g.source_id
|
|
LEFT JOIN md_contact cr
|
|
ON cr.company_id = g.company_id AND cr.id = r.contact_id
|
|
LEFT JOIN td_payment p
|
|
ON g.source_type = 'payment'
|
|
AND p.company_id = g.company_id AND p.id = g.source_id
|
|
LEFT JOIN md_contact cp
|
|
ON cp.company_id = g.company_id AND cp.id = p.contact_id
|
|
LEFT JOIN td_purchase_order po
|
|
ON g.source_type = 'purchase_order'
|
|
AND po.company_id = g.company_id AND po.id = g.source_id
|
|
LEFT JOIN md_contact cpo
|
|
ON cpo.company_id = g.company_id AND cpo.id = po.contact_id
|
|
WHERE " . implode(' AND ', $where) . "
|
|
GROUP BY
|
|
g.id, g.source_type, g.source_id, g.reference, g.description, g.period,
|
|
g.current_version, g.formula_id, f.formula_name, g.created_at, g.updated_at,
|
|
i.invoice_number, r.receipt_number, p.payment_number, po.po_number,
|
|
ci.contact_name, cr.contact_name, cp.contact_name, cpo.contact_name
|
|
ORDER BY g.created_at DESC, g.id DESC
|
|
";
|
|
|
|
$sth = $this->pdo->prepare($sql);
|
|
$sth->execute($params);
|
|
return $sth->fetchAll(PDO::FETCH_ASSOC);
|
|
}
|
|
|
|
public function getJournalDetail(int $gl_id): array
|
|
{
|
|
if ($gl_id <= 0) {
|
|
throw new Exception('gl_id is required.');
|
|
}
|
|
|
|
$sth = $this->pdo->prepare(
|
|
"SELECT g.*,
|
|
DATE_FORMAT(g.created_at, '%d/%m/%Y %H:%i') AS posted_at,
|
|
DATE_FORMAT(g.updated_at, '%d/%m/%Y %H:%i') AS updated_at_fmt,
|
|
DATE_FORMAT(g.journal_date, '%d/%m/%Y') AS journal_date_fmt,
|
|
COALESCE(f.formula_name, '') AS formula_name
|
|
FROM td_gl g
|
|
LEFT JOIN md_account_formula f
|
|
ON f.company_id = g.company_id AND f.id = g.formula_id
|
|
WHERE g.company_id = :cid AND g.id = :gl_id
|
|
LIMIT 1"
|
|
);
|
|
$sth->execute([':cid' => $this->companyId, ':gl_id' => $gl_id]);
|
|
$header = $sth->fetch(PDO::FETCH_ASSOC);
|
|
|
|
if (!$header) {
|
|
throw new Exception('GL entry not found.');
|
|
}
|
|
|
|
$sth = $this->pdo->prepare(
|
|
"SELECT gi.account_code, gi.department_id, gi.debit, gi.credit, gi.description,
|
|
COALESCE(a.account_name, '') AS account_name,
|
|
COALESCE(d.dept_code, '') AS dept_code,
|
|
COALESCE(d.dept_name, '') AS dept_name
|
|
FROM td_gl_item gi
|
|
LEFT JOIN md_account a
|
|
ON a.company_id = gi.company_id AND a.account_code = gi.account_code
|
|
LEFT JOIN md_department d
|
|
ON d.company_id = gi.company_id AND d.id = gi.department_id
|
|
WHERE gi.company_id = :cid AND gi.gl_id = :gl_id
|
|
ORDER BY gi.id ASC"
|
|
);
|
|
$sth->execute([':cid' => $this->companyId, ':gl_id' => $gl_id]);
|
|
|
|
return [
|
|
'header' => $header,
|
|
'lines' => $sth->fetchAll(PDO::FETCH_ASSOC),
|
|
];
|
|
}
|
|
|
|
private function getInvoicePostableDocuments(string $doc_type, string $date_from, string $date_to, int $formula_id): array
|
|
{
|
|
$requires_product_mapping = $this->formulaRequiresProductMapping($doc_type, $formula_id);
|
|
$mapping_column = in_array($doc_type, ['invoice', 'credit_note'], true)
|
|
? 'sales_account_code'
|
|
: 'purchase_account_code';
|
|
|
|
$mapping_select = $requires_product_mapping
|
|
? ", (
|
|
SELECT COUNT(*)
|
|
FROM td_invoice_item ii
|
|
LEFT JOIN md_product p
|
|
ON p.company_id = ii.company_id
|
|
AND p.sku = ii.product_sku
|
|
WHERE ii.company_id = i.company_id
|
|
AND ii.invoice_id = i.id
|
|
AND ABS(ii.total_price) > 0.0000001
|
|
AND COALESCE(NULLIF(p.{$mapping_column}, ''), '') = ''
|
|
) AS product_mapping_missing"
|
|
: ", 0 AS product_mapping_missing";
|
|
|
|
$where = ['i.company_id = :cid', 'i.doc_type = :doc_type', 'i.status != 4'];
|
|
$params = [':cid' => $this->companyId, ':doc_type' => $doc_type, ':source_type' => $doc_type];
|
|
if ($date_from) { $where[] = 'i.issued_date >= :date_from'; $params[':date_from'] = $date_from; }
|
|
if ($date_to) { $where[] = 'i.issued_date <= :date_to'; $params[':date_to'] = $date_to; }
|
|
|
|
$sth = $this->pdo->prepare(
|
|
"SELECT i.id,
|
|
i.invoice_number AS doc_number,
|
|
COALESCE(c.contact_name, '') AS contact_name,
|
|
i.grand_total,
|
|
i.issued_date AS doc_date,
|
|
IF(g.id IS NULL, 0, 1) AS gl_status,
|
|
COALESCE(g.formula_id, 0) AS gl_formula_id,
|
|
" . ($requires_product_mapping ? '1' : '0') . " AS product_mapping_checked
|
|
{$mapping_select}
|
|
FROM td_invoice i
|
|
LEFT JOIN md_contact c
|
|
ON c.company_id = i.company_id AND c.id = i.contact_id
|
|
LEFT JOIN td_gl g
|
|
ON g.company_id = i.company_id
|
|
AND g.source_type = :source_type
|
|
AND g.source_id = i.id
|
|
WHERE " . implode(' AND ', $where) . "
|
|
ORDER BY i.issued_date DESC, i.id DESC"
|
|
);
|
|
$sth->execute($params);
|
|
return $sth->fetchAll(PDO::FETCH_ASSOC);
|
|
}
|
|
|
|
private function getReceiptPostableDocuments(string $date_from, string $date_to): array
|
|
{
|
|
$where = ['r.company_id = :cid', 'r.status = 1'];
|
|
$params = [':cid' => $this->companyId];
|
|
if ($date_from) { $where[] = 'r.receipt_date >= :date_from'; $params[':date_from'] = $date_from; }
|
|
if ($date_to) { $where[] = 'r.receipt_date <= :date_to'; $params[':date_to'] = $date_to; }
|
|
|
|
$sth = $this->pdo->prepare(
|
|
"SELECT r.id,
|
|
r.receipt_number AS doc_number,
|
|
COALESCE(c.contact_name, '') AS contact_name,
|
|
r.amount AS grand_total,
|
|
r.receipt_date AS doc_date,
|
|
IF(g.id IS NULL, 0, 1) AS gl_status,
|
|
COALESCE(g.formula_id, 0) AS gl_formula_id,
|
|
0 AS product_mapping_checked,
|
|
0 AS product_mapping_missing
|
|
FROM td_receipt r
|
|
LEFT JOIN md_contact c
|
|
ON c.company_id = r.company_id AND c.id = r.contact_id
|
|
LEFT JOIN td_gl g
|
|
ON g.company_id = r.company_id
|
|
AND g.source_type = 'receipt'
|
|
AND g.source_id = r.id
|
|
WHERE " . implode(' AND ', $where) . "
|
|
ORDER BY r.receipt_date DESC, r.id DESC"
|
|
);
|
|
$sth->execute($params);
|
|
return $sth->fetchAll(PDO::FETCH_ASSOC);
|
|
}
|
|
|
|
private function getPaymentPostableDocuments(string $date_from, string $date_to): array
|
|
{
|
|
$where = ['r.company_id = :cid', 'r.status = 1'];
|
|
$params = [':cid' => $this->companyId];
|
|
if ($date_from) { $where[] = 'r.payment_date >= :date_from'; $params[':date_from'] = $date_from; }
|
|
if ($date_to) { $where[] = 'r.payment_date <= :date_to'; $params[':date_to'] = $date_to; }
|
|
|
|
$sth = $this->pdo->prepare(
|
|
"SELECT r.id,
|
|
r.payment_number AS doc_number,
|
|
COALESCE(c.contact_name, '') AS contact_name,
|
|
r.amount AS grand_total,
|
|
r.payment_date AS doc_date,
|
|
IF(g.id IS NULL, 0, 1) AS gl_status,
|
|
COALESCE(g.formula_id, 0) AS gl_formula_id,
|
|
0 AS product_mapping_checked,
|
|
0 AS product_mapping_missing
|
|
FROM td_payment r
|
|
LEFT JOIN md_contact c
|
|
ON c.company_id = r.company_id AND c.id = r.contact_id
|
|
LEFT JOIN td_gl g
|
|
ON g.company_id = r.company_id
|
|
AND g.source_type = 'payment'
|
|
AND g.source_id = r.id
|
|
WHERE " . implode(' AND ', $where) . "
|
|
ORDER BY r.payment_date DESC, r.id DESC"
|
|
);
|
|
$sth->execute($params);
|
|
return $sth->fetchAll(PDO::FETCH_ASSOC);
|
|
}
|
|
|
|
private function getPurchaseOrderPostableDocuments(string $date_from, string $date_to, int $formula_id): array
|
|
{
|
|
$requires_mapping = $this->formulaRequiresProductMapping('purchase_order', $formula_id);
|
|
|
|
$mapping_select = $requires_mapping
|
|
? ", (
|
|
SELECT COUNT(*)
|
|
FROM td_purchase_order_item poi
|
|
LEFT JOIN md_product p
|
|
ON p.company_id = poi.company_id
|
|
AND p.sku = poi.product_sku
|
|
WHERE poi.company_id = po.company_id
|
|
AND poi.order_id = po.id
|
|
AND ABS(poi.total_price) > 0.0000001
|
|
AND COALESCE(NULLIF(p.purchase_account_code, ''), '') = ''
|
|
) AS product_mapping_missing"
|
|
: ", 0 AS product_mapping_missing";
|
|
|
|
$where = ['po.company_id = :cid', 'po.status = 1'];
|
|
$params = [':cid' => $this->companyId];
|
|
if ($date_from) { $where[] = 'po.po_date >= :date_from'; $params[':date_from'] = $date_from; }
|
|
if ($date_to) { $where[] = 'po.po_date <= :date_to'; $params[':date_to'] = $date_to; }
|
|
|
|
$sth = $this->pdo->prepare(
|
|
"SELECT po.id,
|
|
po.po_number AS doc_number,
|
|
COALESCE(c.contact_name, '') AS contact_name,
|
|
po.grand_total,
|
|
po.po_date AS doc_date,
|
|
IF(g.id IS NULL, 0, 1) AS gl_status,
|
|
COALESCE(g.formula_id, 0) AS gl_formula_id,
|
|
" . ($requires_mapping ? '1' : '0') . " AS product_mapping_checked
|
|
{$mapping_select}
|
|
FROM td_purchase_order po
|
|
LEFT JOIN md_contact c
|
|
ON c.company_id = po.company_id AND c.id = po.contact_id
|
|
LEFT JOIN td_gl g
|
|
ON g.company_id = po.company_id
|
|
AND g.source_type = 'purchase_order'
|
|
AND g.source_id = po.id
|
|
WHERE " . implode(' AND ', $where) . "
|
|
ORDER BY po.po_date DESC, po.id DESC"
|
|
);
|
|
$sth->execute($params);
|
|
return $sth->fetchAll(PDO::FETCH_ASSOC);
|
|
}
|
|
|
|
private function formulaRequiresProductMapping(string $doc_type, int $formula_id): bool
|
|
{
|
|
if ($formula_id <= 0 || !in_array($doc_type, $this->mappingTypes, true)) {
|
|
return false;
|
|
}
|
|
|
|
$sth = $this->pdo->prepare(
|
|
"SELECT COUNT(*)
|
|
FROM md_account_formula_item fi
|
|
JOIN md_account_formula f
|
|
ON f.company_id = fi.company_id
|
|
AND f.id = fi.formula_id
|
|
WHERE fi.company_id = :cid
|
|
AND fi.formula_id = :formula_id
|
|
AND f.document_type = :doc_type
|
|
AND fi.amount_key = 'total'
|
|
AND f.status = 1"
|
|
);
|
|
$sth->execute([
|
|
':cid' => $this->companyId,
|
|
':formula_id' => $formula_id,
|
|
':doc_type' => $doc_type,
|
|
]);
|
|
return ((int)$sth->fetchColumn()) > 0;
|
|
}
|
|
|
|
private function parseDate(string $value): string
|
|
{
|
|
$value = trim($value);
|
|
if ($value === '') return '';
|
|
if (preg_match('#^(\d{2})/(\d{2})/(\d{4})$#', $value, $m)) {
|
|
return "{$m[3]}-{$m[2]}-{$m[1]}";
|
|
}
|
|
return $value;
|
|
}
|
|
}
|
|
?>
|