accounting workflows
This commit is contained in:
@@ -15,15 +15,16 @@
|
||||
exit(json_encode($answer));
|
||||
}
|
||||
|
||||
$csm = new CompanySettingManager($pdo1, $company_id);
|
||||
$auto_cn = (int)$csm->get('auto_invoice_and_credit_note') === 1;
|
||||
$csm = new CompanySettingManager($pdo1, $company_id);
|
||||
$auto_cn = (int)$csm->get('auto_invoice_and_credit_note') === 1;
|
||||
$auto_approve = (int)$csm->get('default_stock_status') === 1;
|
||||
|
||||
try {
|
||||
dbTransaction($pdo2, function($pdo) use ($id, $company_id, $logging, $uuid, $auto_cn) {
|
||||
dbTransaction($pdo2, function($pdo) use ($id, $company_id, $logging, $uuid, $auto_cn, $auto_approve) {
|
||||
$ret = new ReturnManager($pdo, $company_id);
|
||||
$whMgmt = new WarehouseManager($pdo, $company_id);
|
||||
$invMgmt = new InvoiceManager($pdo, $company_id);
|
||||
$ret->confirmReturn($id, $uuid, $logging, $whMgmt, $invMgmt, $auto_cn);
|
||||
$ret->confirmReturn($id, $uuid, $logging, $whMgmt, $invMgmt, $auto_cn, $auto_approve);
|
||||
});
|
||||
|
||||
$answer['success'] = 1;
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
require_role($user_role, ['owner', 'admin', 'staff']);
|
||||
require '../../../assets/utils/classes/InvoiceManager.php';
|
||||
|
||||
$id = (int)($data['id'] ?? 0);
|
||||
$id = (int)($data['id'] ?? ($data['invoice_id'] ?? 0));
|
||||
$due_date = trim((string)($data['due_date'] ?? ''));
|
||||
|
||||
if (!$id) {
|
||||
$answer['message'] = 'Invalid ID.';
|
||||
@@ -13,9 +14,9 @@
|
||||
}
|
||||
|
||||
try {
|
||||
dbTransaction($pdo2, function($pdo) use ($id, $company_id, $logging) {
|
||||
dbTransaction($pdo2, function($pdo) use ($id, $due_date, $company_id, $logging) {
|
||||
$inv = new InvoiceManager($pdo, $company_id);
|
||||
$inv->issueInvoice($id, $logging);
|
||||
$inv->issueInvoice($id, $logging, $due_date ?: null);
|
||||
});
|
||||
|
||||
$answer['success'] = 1;
|
||||
@@ -31,4 +32,4 @@
|
||||
}
|
||||
|
||||
exit(json_encode($answer));
|
||||
?>
|
||||
?>
|
||||
|
||||
@@ -5,6 +5,18 @@
|
||||
require '../../../assets/utils/classes/InvoiceManager.php';
|
||||
|
||||
try {
|
||||
$action = $data['action'] ?? 'update';
|
||||
|
||||
if ($action === 'save_formula') {
|
||||
$id = (int)($data['id'] ?? 0);
|
||||
$formula_id = isset($data['formula_id']) && (int)$data['formula_id'] > 0
|
||||
? (int)$data['formula_id'] : null;
|
||||
$inv = new InvoiceManager($pdo2, $company_id);
|
||||
$inv->saveFormula($id, $formula_id);
|
||||
$answer['success'] = 1;
|
||||
exit(json_encode($answer));
|
||||
}
|
||||
|
||||
dbTransaction($pdo2, function($pdo) use ($data, $company_id, $logging) {
|
||||
$inv = new InvoiceManager($pdo, $company_id);
|
||||
$inv->saveInvoice($data, $logging);
|
||||
|
||||
@@ -8,19 +8,17 @@
|
||||
$data['items'] = json_decode($data['items'] ?? '[]', true) ?: [];
|
||||
|
||||
try {
|
||||
$new_id = null;
|
||||
$quotation_id = (int)($data['quotation_id'] ?? 0);
|
||||
$new_id = null;
|
||||
$source = trim((string)($data['source'] ?? ''));
|
||||
$source_id = (int)($data['source_id'] ?? 0);
|
||||
|
||||
dbTransaction($pdo2, function($pdo) use ($data, $company_id, $logging, $quotation_id, &$new_id) {
|
||||
dbTransaction($pdo2, function($pdo) use ($data, $company_id, $logging, $source, $source_id, &$new_id) {
|
||||
$order = new OrderManager($pdo, $company_id);
|
||||
$new_id = $order->saveOrder($data, $logging);
|
||||
|
||||
// On new order created from a quotation: link both records
|
||||
if ($new_id && $quotation_id > 0) {
|
||||
$pdo->prepare(
|
||||
"UPDATE ac_quotation SET order_id = :order_id, status = 5
|
||||
WHERE id = :id AND company_id = :cid AND status = 2"
|
||||
)->execute([':order_id' => $new_id, ':id' => $quotation_id, ':cid' => $company_id]);
|
||||
if ($new_id && $source === 'quotation' && $source_id > 0) {
|
||||
$order->linkQuotationToOrder($source_id, $new_id);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -39,4 +37,4 @@
|
||||
}
|
||||
|
||||
exit(json_encode($answer));
|
||||
?>
|
||||
?>
|
||||
|
||||
@@ -4,9 +4,21 @@
|
||||
require_role($user_role, ['owner', 'admin', 'staff']);
|
||||
require '../../../assets/utils/classes/ReturnManager.php';
|
||||
|
||||
$action = $data['action'] ?? 'save';
|
||||
$data['items'] = json_decode($data['items'] ?? '[]', true) ?: [];
|
||||
|
||||
try {
|
||||
if ($action === 'save_formula') {
|
||||
$id = (int)($data['id'] ?? 0);
|
||||
$formula_id = isset($data['formula_id']) && (int)$data['formula_id'] > 0
|
||||
? (int)$data['formula_id'] : null;
|
||||
if (!$id) throw new Exception('Missing return id.');
|
||||
$ret = new ReturnManager($pdo2, $company_id);
|
||||
$ret->saveFormula($id, $formula_id);
|
||||
$answer['success'] = 1;
|
||||
exit(json_encode($answer));
|
||||
}
|
||||
|
||||
$new_id = null;
|
||||
|
||||
dbTransaction($pdo2, function($pdo) use ($data, $company_id, $logging, &$new_id) {
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
session_start();
|
||||
require '../../../assets/utils/db_auth.php';
|
||||
require_role($user_role, ['owner', 'admin', 'staff']);
|
||||
require '../../../assets/utils/classes/InvoiceManager.php';
|
||||
|
||||
$return_id = (int)($data['return_id'] ?? 0);
|
||||
|
||||
if (!$return_id) {
|
||||
$answer['message'] = 'Invalid return ID.';
|
||||
http_response_code(400);
|
||||
exit(json_encode($answer));
|
||||
}
|
||||
|
||||
try {
|
||||
$new_id = null;
|
||||
|
||||
dbTransaction($pdo2, function($pdo) use ($return_id, $company_id, $logging, &$new_id) {
|
||||
$inv = new InvoiceManager($pdo, $company_id);
|
||||
$new_id = $inv->createFromReturn($return_id, $logging);
|
||||
});
|
||||
|
||||
$answer['success'] = 1;
|
||||
$answer['new_id'] = $new_id;
|
||||
|
||||
} catch (PDOException $e) {
|
||||
$answer['message'] = 'Database error, please try again.';
|
||||
http_response_code(500);
|
||||
|
||||
} catch (Exception $e) {
|
||||
$answer['message'] = $e->getMessage();
|
||||
http_response_code(400);
|
||||
}
|
||||
|
||||
exit(json_encode($answer));
|
||||
?>
|
||||
@@ -17,41 +17,23 @@
|
||||
|
||||
// Fetch linked credit notes for invoice detail page
|
||||
if ($result['doc_type'] === 'invoice') {
|
||||
$sth = $pdo2->prepare(
|
||||
"SELECT * FROM td_invoice
|
||||
WHERE company_id = :company_id
|
||||
AND ref_invoice_id = :ref_id
|
||||
AND doc_type = 'credit_note'
|
||||
ORDER BY id DESC"
|
||||
);
|
||||
$sth->execute([':company_id' => $company_id, ':ref_id' => $id]);
|
||||
$answer['credit_notes'] = $sth->fetchAll(PDO::FETCH_ASSOC);
|
||||
$answer['credit_notes'] = $inv->getLinkedCreditNotes($id, 'credit_note');
|
||||
}
|
||||
|
||||
if ($result['doc_type'] === 'purchase_invoice') {
|
||||
$answer['supplier_credit_notes'] = $inv->getLinkedCreditNotes($id, 'supplier_credit_note');
|
||||
}
|
||||
} else {
|
||||
$order_id = (int)($data['order_id'] ?? 0);
|
||||
$doc_type = $data['doc_type'] ?? '';
|
||||
$answer['output'] = $inv->getInvoiceList($order_id, $doc_type);
|
||||
|
||||
// For credit note tab — join ref invoice number
|
||||
if ($doc_type === 'credit_note') {
|
||||
$rows = $answer['output'];
|
||||
foreach ($rows as &$row) {
|
||||
if ((int)$row['ref_invoice_id'] > 0) {
|
||||
$sth = $pdo2->prepare(
|
||||
"SELECT invoice_number FROM td_invoice
|
||||
WHERE id = :id AND company_id = :company_id"
|
||||
);
|
||||
$sth->execute([':id' => $row['ref_invoice_id'], ':company_id' => $company_id]);
|
||||
$row['ref_invoice_number'] = $sth->fetchColumn() ?: '—';
|
||||
} else {
|
||||
$row['ref_invoice_number'] = '—';
|
||||
}
|
||||
}
|
||||
unset($row);
|
||||
$answer['output'] = $rows;
|
||||
// For credit note / debit note tabs — join ref invoice number
|
||||
if ($doc_type === 'credit_note' || $doc_type === 'supplier_credit_note') {
|
||||
$answer['output'] = $inv->addReferenceInvoiceNumbers($answer['output']);
|
||||
}
|
||||
}
|
||||
|
||||
$answer['success'] = 1;
|
||||
exit(json_encode($answer));
|
||||
?>
|
||||
?>
|
||||
|
||||
@@ -16,85 +16,10 @@
|
||||
}
|
||||
$answer['output'] = $result;
|
||||
|
||||
// Check if an active invoice already exists for this order
|
||||
$sth = $pdo2->prepare(
|
||||
"SELECT COUNT(*) FROM td_invoice
|
||||
WHERE company_id = :company_id
|
||||
AND order_id = :order_id
|
||||
AND doc_type = 'invoice'
|
||||
AND status != 4"
|
||||
);
|
||||
$sth->execute([':company_id' => $company_id, ':order_id' => $id]);
|
||||
$answer['has_active_invoice'] = (int)$sth->fetchColumn() > 0;
|
||||
|
||||
// Calculate returnable items — order items minus already confirmed returns
|
||||
$order_items = $result['items'] ?? '[]';
|
||||
|
||||
// Aggregate returned qty per item_id from confirmed returns
|
||||
$sth = $pdo2->prepare(
|
||||
"SELECT items FROM td_return
|
||||
WHERE company_id = :company_id
|
||||
AND order_id = :order_id
|
||||
AND status = 1"
|
||||
);
|
||||
$sth->execute([':company_id' => $company_id, ':order_id' => $id]);
|
||||
$returned_qty = [];
|
||||
|
||||
foreach ($sth->fetchAll(PDO::FETCH_COLUMN) as $items_json) {
|
||||
$items = json_decode($items_json, true) ?: [];
|
||||
foreach ($items as $item) {
|
||||
$item_id = (int)($item['item_id'] ?? -1);
|
||||
if ($item_id < 0) continue;
|
||||
$returned_qty[$item_id] = ($returned_qty[$item_id] ?? 0) + (float)$item['quantity'];
|
||||
}
|
||||
}
|
||||
|
||||
$returnable = [];
|
||||
foreach ($order_items as $i => $item) {
|
||||
$already_returned = $returned_qty[$i] ?? 0;
|
||||
$remaining = (float)$item['quantity'] - $already_returned;
|
||||
if ($remaining > 0) {
|
||||
$stock_out_warehouse_id = (int)($item['warehouse_id'] ?? 0);
|
||||
$stock_out_id = (int)($item['stock_out_id'] ?? 0);
|
||||
|
||||
if ($stock_out_warehouse_id > 0 && $stock_out_id > 0) {
|
||||
$table = 'td_stock_' . $stock_out_warehouse_id;
|
||||
$sth = $pdo2->prepare(
|
||||
"SELECT id, status, zone, aisle, rack, lot_number, serial_number
|
||||
FROM `{$table}`
|
||||
WHERE company_id = :company_id
|
||||
AND id = :id
|
||||
AND type = 'out'
|
||||
LIMIT 1"
|
||||
);
|
||||
$sth->execute([
|
||||
':company_id' => $company_id,
|
||||
':id' => $stock_out_id,
|
||||
]);
|
||||
$stock_out = $sth->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($stock_out) {
|
||||
$item['zone'] = $stock_out['zone'] ?? ($item['zone'] ?? '');
|
||||
$item['aisle'] = $stock_out['aisle'] ?? ($item['aisle'] ?? '');
|
||||
$item['rack'] = $stock_out['rack'] ?? ($item['rack'] ?? '');
|
||||
$item['lot_number'] = $stock_out['lot_number'] ?? ($item['lot_number'] ?? '');
|
||||
$item['serial_number'] = $stock_out['serial_number'] ?? ($item['serial_number'] ?? '');
|
||||
$item['stock_out_status'] = (int)$stock_out['status'];
|
||||
}
|
||||
}
|
||||
|
||||
$returnable[] = array_merge($item, [
|
||||
'item_id' => $i,
|
||||
'original_qty' => (float)$item['quantity'],
|
||||
'returned_qty' => $already_returned,
|
||||
'returnable_qty' => $remaining,
|
||||
'stock_out_warehouse_id' => $stock_out_warehouse_id,
|
||||
'stock_out_status' => (int)($item['stock_out_status'] ?? 0),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$answer['returnable_items'] = $returnable;
|
||||
$active_invoice_id = $order->getActiveInvoiceId($id);
|
||||
$answer['has_active_invoice'] = $active_invoice_id > 0;
|
||||
$answer['active_invoice_id'] = $active_invoice_id;
|
||||
$answer['returnable_items'] = $order->getReturnableItems($result);
|
||||
} else {
|
||||
$answer['output'] = $order->getOrderList();
|
||||
}
|
||||
|
||||
@@ -13,6 +13,11 @@
|
||||
$answer['message'] = 'Return not found.';
|
||||
exit(json_encode($answer));
|
||||
}
|
||||
|
||||
$active_cn_id = $ret->getActiveCreditNoteId($id);
|
||||
$result['has_active_cn'] = $active_cn_id > 0 ? 1 : 0;
|
||||
$result['active_cn_id'] = $active_cn_id;
|
||||
|
||||
$answer['output'] = $result;
|
||||
} else {
|
||||
$order_id = (int)($data['order_id'] ?? 0);
|
||||
@@ -21,4 +26,4 @@
|
||||
|
||||
$answer['success'] = 1;
|
||||
exit(json_encode($answer));
|
||||
?>
|
||||
?>
|
||||
|
||||
@@ -13,8 +13,9 @@
|
||||
exit(json_encode($answer));
|
||||
}
|
||||
|
||||
// 2=paid 3=overdue 4=void — issue (0→1) handled by issue_invoice.php
|
||||
if (!in_array($status, [2, 3, 4], true)) {
|
||||
// Manual status changes are limited to void. Paid is handled by payments;
|
||||
// overdue is calculated from due_date and is not stored as a status.
|
||||
if ($status !== 4) {
|
||||
$answer['message'] = 'Invalid status value.';
|
||||
http_response_code(400);
|
||||
exit(json_encode($answer));
|
||||
@@ -22,38 +23,8 @@
|
||||
|
||||
try {
|
||||
dbTransaction($pdo2, function($pdo) use ($id, $status, $company_id, $logging) {
|
||||
|
||||
$sth = $pdo->prepare(
|
||||
"SELECT status, doc_type, `log` FROM td_invoice
|
||||
WHERE company_id = :company_id AND id = :id"
|
||||
);
|
||||
$sth->execute([':company_id' => $company_id, ':id' => $id]);
|
||||
$row = $sth->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$row) throw new Exception("Invoice not found.");
|
||||
if ($row['doc_type'] !== 'invoice') throw new Exception("Only invoices can be updated this way.");
|
||||
|
||||
$current = (int)$row['status'];
|
||||
|
||||
if ($current === 4) throw new Exception("Invoice is already void.");
|
||||
if ($current === 0) throw new Exception("Issue the invoice before changing payment status.");
|
||||
|
||||
$log = json_decode($row['log'] ?? '[]', true) ?: [];
|
||||
$log[] = array_merge($logging, [
|
||||
'action' => 'update_status',
|
||||
'status' => $status,
|
||||
'previous_status' => $current,
|
||||
]);
|
||||
|
||||
$pdo->prepare(
|
||||
"UPDATE td_invoice SET status = :status, `log` = :log
|
||||
WHERE id = :id AND company_id = :company_id"
|
||||
)->execute([
|
||||
':status' => $status,
|
||||
':log' => json_encode($log),
|
||||
':id' => $id,
|
||||
':company_id' => $company_id,
|
||||
]);
|
||||
$inv = new InvoiceManager($pdo, $company_id);
|
||||
$inv->voidInvoice($id, array_merge($logging, ['action' => 'update_status', 'status' => $status]));
|
||||
});
|
||||
|
||||
$answer['success'] = 1;
|
||||
@@ -69,4 +40,4 @@
|
||||
}
|
||||
|
||||
exit(json_encode($answer));
|
||||
?>
|
||||
?>
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
require '../../../assets/utils/classes/CompanySettingManager.php';
|
||||
|
||||
$id = (int)($data['id'] ?? 0);
|
||||
$fulfillment_status = (int)($data['fulfillment_status'] ?? 0);
|
||||
$tracking_number = trim((string)($data['shipping_tracking_number'] ?? ''));
|
||||
|
||||
if (!$id) {
|
||||
@@ -19,9 +18,9 @@
|
||||
$auto_complete = (int)$csm->get('auto_complete_on_ship') === 1;
|
||||
|
||||
try {
|
||||
dbTransaction($pdo2, function($pdo) use ($id, $fulfillment_status, $tracking_number, $company_id, $logging, $auto_complete) {
|
||||
dbTransaction($pdo2, function($pdo) use ($id, $tracking_number, $company_id, $logging, $auto_complete) {
|
||||
$order = new OrderManager($pdo, $company_id);
|
||||
$order->updateFulfillmentStatus($id, $fulfillment_status, $tracking_number, $logging, $auto_complete);
|
||||
$order->updateShippingTracking($id, $tracking_number, $logging, $auto_complete);
|
||||
});
|
||||
|
||||
$answer['success'] = 1;
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
<?php
|
||||
session_start();
|
||||
require '../../../assets/utils/db_auth.php';
|
||||
require_role($user_role, ['owner', 'admin', 'staff']);
|
||||
|
||||
$id = (int)($data['id'] ?? 0);
|
||||
$payment_status = (int)($data['payment_status'] ?? 0);
|
||||
|
||||
if (!$id) {
|
||||
$answer['message'] = 'Invalid order ID.';
|
||||
http_response_code(400);
|
||||
exit(json_encode($answer));
|
||||
}
|
||||
|
||||
// Validate payment_status range: 0=unpaid 1=paid 2=partial
|
||||
if (!in_array($payment_status, [0, 1, 2], true)) {
|
||||
$answer['message'] = 'Invalid payment status.';
|
||||
http_response_code(400);
|
||||
exit(json_encode($answer));
|
||||
}
|
||||
|
||||
try {
|
||||
dbTransaction($pdo2, function($pdo) use ($id, $payment_status, $company_id, $logging) {
|
||||
|
||||
// Load order — verify ownership and that order is not cancelled
|
||||
$sth = $pdo->prepare(
|
||||
"SELECT status, payment_status, `log` FROM td_order
|
||||
WHERE company_id = :company_id AND id = :id"
|
||||
);
|
||||
$sth->execute([':company_id' => $company_id, ':id' => $id]);
|
||||
$row = $sth->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$row) throw new Exception("Order not found.");
|
||||
if ((int)$row['status'] === -1) throw new Exception("Cannot update payment status of a cancelled order.");
|
||||
|
||||
$log = json_decode($row['log'] ?? '[]', true) ?: [];
|
||||
$log[] = array_merge($logging, [
|
||||
'action' => 'update_payment_status',
|
||||
'payment_status' => $payment_status,
|
||||
'previous_status' => (int)$row['payment_status'],
|
||||
]);
|
||||
|
||||
$pdo->prepare(
|
||||
"UPDATE td_order SET
|
||||
payment_status = :payment_status,
|
||||
`log` = :log
|
||||
WHERE id = :id AND company_id = :company_id"
|
||||
)->execute([
|
||||
':payment_status' => $payment_status,
|
||||
':log' => json_encode($log),
|
||||
':id' => $id,
|
||||
':company_id' => $company_id,
|
||||
]);
|
||||
});
|
||||
|
||||
$answer['success'] = 1;
|
||||
$answer['message'] = 'Payment status updated.';
|
||||
|
||||
} catch (PDOException $e) {
|
||||
$answer['message'] = 'Database error, please try again.';
|
||||
http_response_code(500);
|
||||
|
||||
} catch (Exception $e) {
|
||||
$answer['message'] = $e->getMessage();
|
||||
http_response_code(400);
|
||||
}
|
||||
|
||||
exit(json_encode($answer));
|
||||
?>
|
||||
Reference in New Issue
Block a user