softDelete features

This commit is contained in:
Thanakorn S
2026-05-22 14:07:22 +07:00
parent f04554793e
commit 2410f7bade
59 changed files with 1783 additions and 16 deletions
+106
View File
@@ -1,4 +1,5 @@
<?php
require_once __DIR__ . '/../classes_ac/PostingWindowGuard.php';
/**
* OrderManager
@@ -992,6 +993,111 @@ class OrderManager {
]);
}
/**
* Soft-delete an order by negating company_id on the header, items, and all
* linked stock-out rows. Approved stock-out side effects are reversed first.
* Blocked if any invoice or return (not yet soft-deleted) exists for this order.
*/
public function softDelete(int $id): void
{
$sth = $this->pdo->prepare(
"SELECT * FROM td_order WHERE company_id = :cid AND id = :id"
);
$sth->execute([':cid' => $this->company_id, ':id' => $id]);
$order = $sth->fetch(PDO::FETCH_ASSOC);
if (!$order) throw new Exception('Sales order not found.');
global $pdo1;
if (isset($pdo1) && $pdo1 instanceof PDO) {
$guard = new PostingWindowGuard($pdo1, $this->company_id);
$guard->assertOpenDate($order['order_date'] ?: date('Y-m-d'), 'Order deletion');
}
$sth2 = $this->pdo->prepare(
"SELECT COUNT(*) FROM td_invoice
WHERE company_id = :cid AND order_id = :id"
);
$sth2->execute([':cid' => $this->company_id, ':id' => $id]);
if ((int)$sth2->fetchColumn() > 0) {
throw new Exception('Cannot delete — this order has linked invoices. Delete the invoices first.');
}
$sth3 = $this->pdo->prepare(
"SELECT COUNT(*) FROM td_return
WHERE company_id = :cid AND order_id = :id"
);
$sth3->execute([':cid' => $this->company_id, ':id' => $id]);
if ((int)$sth3->fetchColumn() > 0) {
throw new Exception('Cannot delete — this order has linked returns. Delete the returns first.');
}
// Block if any approved stock-out rows exist; user must reverse via ICS first
$sth4 = $this->pdo->prepare(
"SELECT table_name FROM information_schema.tables
WHERE table_schema = DATABASE() AND table_name LIKE 'td_stock_%'"
);
$sth4->execute();
$tables = $sth4->fetchAll(PDO::FETCH_COLUMN);
foreach ($tables as $table) {
$chk = $this->pdo->prepare(
"SELECT COUNT(*) FROM `{$table}`
WHERE company_id = :cid AND source = 'order' AND source_id = :id AND type = 'out' AND status = 1"
);
$chk->execute([':cid' => $this->company_id, ':id' => $id]);
if ((int)$chk->fetchColumn() > 0) {
throw new Exception('Cannot delete — stock-out for this order has been approved. Reverse via ICS first.');
}
}
// Negate draft/pending stock-out rows (status != 1)
foreach ($tables as $table) {
$this->pdo->prepare(
"UPDATE `{$table}` SET company_id = company_id * -1
WHERE company_id = :cid AND source = 'order' AND source_id = :id AND type = 'out' AND status != 1"
)->execute([':cid' => $this->company_id, ':id' => $id]);
}
// Release converted_qty on source quotation
$source = (string)($order['source'] ?? '');
$source_id = (int)($order['source_id'] ?? 0);
if ($source === 'quotation' && $source_id > 0) {
$sth5 = $this->pdo->prepare(
"SELECT item_id, quantity FROM td_order_item
WHERE order_id = :id AND company_id = :cid ORDER BY item_id"
);
$sth5->execute([':id' => $id, ':cid' => $this->company_id]);
$items = $sth5->fetchAll(PDO::FETCH_ASSOC);
$dec = $this->pdo->prepare(
"UPDATE td_quotation_item
SET converted_qty = GREATEST(0, converted_qty - :qty)
WHERE quotation_id = :qid AND item_id = :item_id AND company_id = :cid"
);
foreach ($items as $item) {
$dec->execute([
':qty' => (float)($item['quantity'] ?? 0),
':qid' => $source_id,
':item_id' => (int)($item['item_id'] ?? 0),
':cid' => $this->company_id,
]);
}
$this->pdo->prepare(
"UPDATE td_quotation SET status = 2
WHERE id = :id AND company_id = :cid AND status = 5"
)->execute([':id' => $source_id, ':cid' => $this->company_id]);
}
$this->pdo->prepare(
"UPDATE td_order_item SET company_id = company_id * -1
WHERE order_id = :id AND company_id = :cid"
)->execute([':id' => $id, ':cid' => $this->company_id]);
$this->pdo->prepare(
"UPDATE td_order SET company_id = company_id * -1
WHERE id = :id AND company_id = :cid"
)->execute([':id' => $id, ':cid' => $this->company_id]);
}
/**
* Update shipping tracking number. Fulfillment itself is derived from
* linked stock-out rows and tracking, not selected manually.