Document lifecycle review: spec updates and C2/M9 code fixes
Spec (docs/reviewed/document-lifecycle.md): - C1: Correct GlManager::delete() description — reversal entry, not hard delete - C3: Clarify PO status 2/3 are derived (receipt_status), never stored - C4: Add invoice status=2 (Paid/Settled) to status table - C5: Add full Receipt/Payment Billing Note lifecycle section - C6: Add Quotation status table and transition rules - C7: Document soft-delete tombstone mechanism (company_id negation) Code (InvoiceManager.php): - C2: voidInvoice() now blocks on active credit notes, posted receipt billing notes, and posted payment billing notes in addition to the existing receipt/payment checks - M9: softDelete() skips assertPostingWindow for draft invoices (status=0) since drafts have no GL entry and no accounting impact Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
5687b562fb
commit
cb36d3b8fd
@@ -1143,7 +1143,7 @@ class InvoiceManager {
|
||||
public function softDelete(int $id): void
|
||||
{
|
||||
$sth = $this->pdo->prepare(
|
||||
"SELECT id, doc_type, issued_date FROM td_invoice
|
||||
"SELECT id, doc_type, status, issued_date FROM td_invoice
|
||||
WHERE company_id = :cid AND id = :id LIMIT 1"
|
||||
);
|
||||
$sth->execute([':cid' => $this->company_id, ':id' => $id]);
|
||||
@@ -1151,7 +1151,10 @@ class InvoiceManager {
|
||||
if (!$row) throw new Exception('Invoice not found.');
|
||||
$doc_type = (string)$row['doc_type'];
|
||||
|
||||
$this->assertPostingWindow($row['issued_date'] ?: date('Y-m-d'), ucfirst(str_replace('_', ' ', $doc_type)) . ' deletion');
|
||||
// Drafts have no GL entry — posting window does not apply.
|
||||
if ((int)$row['status'] !== 0) {
|
||||
$this->assertPostingWindow($row['issued_date'] ?: date('Y-m-d'), ucfirst(str_replace('_', ' ', $doc_type)) . ' deletion');
|
||||
}
|
||||
|
||||
if (in_array($doc_type, ['invoice', 'credit_note'], true)) {
|
||||
$sth2 = $this->pdo->prepare(
|
||||
@@ -1273,6 +1276,35 @@ class InvoiceManager {
|
||||
if ((int)$sth->fetchColumn() > 0) {
|
||||
throw new Exception("Cannot void this document while posted receipts are allocated to it. Void the receipt first.");
|
||||
}
|
||||
|
||||
$sth = $this->pdo->prepare(
|
||||
"SELECT COUNT(*)
|
||||
FROM td_receipt_billing_item bi
|
||||
JOIN td_receipt_billing b
|
||||
ON b.company_id = bi.company_id
|
||||
AND b.id = bi.billing_id
|
||||
AND b.status = 1
|
||||
WHERE bi.company_id = :company_id
|
||||
AND bi.invoice_id = :invoice_id"
|
||||
);
|
||||
$sth->execute([':company_id' => $this->company_id, ':invoice_id' => $id]);
|
||||
if ((int)$sth->fetchColumn() > 0) {
|
||||
throw new Exception("Cannot void this document while posted receipt billing notes are allocated to it. Void the receipt billing note first.");
|
||||
}
|
||||
}
|
||||
|
||||
if ($row['doc_type'] === 'invoice') {
|
||||
$sth = $this->pdo->prepare(
|
||||
"SELECT COUNT(*) FROM td_invoice
|
||||
WHERE company_id = :company_id
|
||||
AND ref_invoice_id = :invoice_id
|
||||
AND doc_type = 'credit_note'
|
||||
AND status != 4"
|
||||
);
|
||||
$sth->execute([':company_id' => $this->company_id, ':invoice_id' => $id]);
|
||||
if ((int)$sth->fetchColumn() > 0) {
|
||||
throw new Exception("Cannot void this invoice while active credit notes reference it. Void the credit notes first.");
|
||||
}
|
||||
}
|
||||
|
||||
if (in_array($row['doc_type'], ['purchase_invoice', 'supplier_credit_note'], true)) {
|
||||
@@ -1290,6 +1322,21 @@ class InvoiceManager {
|
||||
if ((int)$sth->fetchColumn() > 0) {
|
||||
throw new Exception("Cannot void this document while posted payments are allocated to it. Void the payment first.");
|
||||
}
|
||||
|
||||
$sth = $this->pdo->prepare(
|
||||
"SELECT COUNT(*)
|
||||
FROM td_payment_billing_item bi
|
||||
JOIN td_payment_billing b
|
||||
ON b.company_id = bi.company_id
|
||||
AND b.id = bi.billing_id
|
||||
AND b.status = 1
|
||||
WHERE bi.company_id = :company_id
|
||||
AND bi.invoice_id = :invoice_id"
|
||||
);
|
||||
$sth->execute([':company_id' => $this->company_id, ':invoice_id' => $id]);
|
||||
if ((int)$sth->fetchColumn() > 0) {
|
||||
throw new Exception("Cannot void this document while posted payment billing notes are allocated to it. Void the payment billing note first.");
|
||||
}
|
||||
}
|
||||
|
||||
$log = json_decode($row['log'] ?? '[]', true) ?: [];
|
||||
|
||||
Reference in New Issue
Block a user