[OOP] contact,inventory,ics,dashboard

This commit is contained in:
Thanakorn S
2026-04-28 15:44:57 +07:00
parent 3d3b497c08
commit 2061624641
49 changed files with 1946 additions and 2146 deletions
+5 -20
View File
@@ -1,25 +1,10 @@
<?php
<?php
session_start();
require '../../../assets/utils/db_auth.php';
require '../../../assets/utils/classes/ContactManager.php';
$sql = "SELECT *
FROM md_contact
WHERE
company_id = :company_id and
contact_name like :keyword
LIMIT 50
";
$sth = $pdo2->prepare($sql);
$sth->execute([
":company_id" => $company_id,
":keyword" => '%'.$data["keyword"].'%'
]);
db_check($sth, $answer);
$result = $sth->fetchAll(PDO::FETCH_ASSOC);
$answer["result"] = $result;
$answer["success"] = 1;
$contact = new ContactManager($pdo2, $company_id);
$answer['result'] = $contact->searchContact($data['keyword'] ?? '');
$answer['success'] = 1;
exit(json_encode($answer));
?>
+10 -107
View File
@@ -1,122 +1,25 @@
<?php
<?php
session_start();
require '../../../assets/utils/db_auth.php';
require '../../../assets/utils/classes/StockManager.php';
require '../../../assets/utils/classes/WarehouseManager.php';
try {
dbTransaction($pdo2, function($pdo) use ($data, $company_id, $logging, $uuid, &$answer) {
$id = (int)$data['id'];
$warehouse_id = (int)$data["warehouse"];
$whMgmt = new WarehouseManager($pdo, $company_id);
$warehouse = $whMgmt->getStockContext($warehouse_id, $id);
$row = $warehouse['row'] ?? [];
$table = $warehouse['table'];
// Append this edit/create event to td_stock_<wh>.log (stock record history)
$table_log = json_decode($row['log'] ?? '[]', true);
$table_log[] = $logging;
if ($id > 0) {
// UPDATE existing stock_in record
$sql = "UPDATE `$table` SET
`contact_id` = :contact_id,
`description` = :description,
`log` = :log
WHERE id = :id AND company_id = :company_id";
$sth = $pdo->prepare($sql);
$sth->execute([
":id" => $id,
":company_id" => $company_id,
":contact_id" => (int)$data["contact_id"],
":description" => $data["description"],
":log" => json_encode($table_log),
]);
} else {
// Upsert md_lot if lot_number + expiry_date provided
$lot_number = $data["lot_number"] ?: null;
$expiry_date = $data["expiry_date"] ?: null;
if ($lot_number && $expiry_date) {
$sth = $pdo->prepare("
INSERT INTO md_lot (company_id, product_sku, lot_number, expiry_date)
VALUES (:company_id, :product_sku, :lot_number, :expiry_date)
ON DUPLICATE KEY UPDATE expiry_date = expiry_date
");
$sth->execute([
":company_id" => $company_id,
":product_sku" => $data["product_sku"],
":lot_number" => $lot_number,
":expiry_date" => $expiry_date,
]);
}
// INSERT new stock_in record
$sql = "INSERT INTO `$table`
(uuid, company_id, `date`, product_sku, `in`, zone, aisle, rack, contact_id, `description`, `log`, `type`, `lot_number`, `serial_number`)
VALUES
(:uuid, :company_id, :date, :product_sku, :quantity, :zone, :aisle, :rack, :contact_id, :description, :log, 'in', :lot_number, :serial_number)";
$sth = $pdo->prepare($sql);
$sth->execute([
":uuid" => $uuid,
":company_id" => $company_id,
":date" => date('Y-m-d H:i:s'),
":product_sku" => $data["product_sku"],
":quantity" => $data["quantity"],
":zone" => $data["zone"],
":aisle" => $data["aisle"],
":rack" => $data["rack"],
":contact_id" => (int)$data["contact_id"],
":description" => $data["description"],
":log" => json_encode($table_log),
":lot_number" => $lot_number,
":serial_number" => $data["serial_number"] ?: null,
]);
// Grab the newly-generated ID to link the rack
$td_stock_id = (int) $pdo->lastInsertId();
// Link the rack to this new stock record
$whMgmt->occupyRack(
$warehouse_id,
$data['zone'], $data['aisle'], $data['rack'],
$data['product_sku'],
$td_stock_id
);
// Balance adjustment handles both INSERT (delta = full qty)
// and UPDATE (delta = new - old) via the same formula.
$whMgmt->adjustBalance(
'in',
$warehouse_id,
$data["product_sku"],
$row['in'] ?? 0,
$data["quantity"]
);
}
dbTransaction($pdo2, function($pdo) use ($data, $company_id, $logging, $uuid) {
$stock = new StockManager($pdo, $company_id);
$stock->saveStockIn($data, $logging, $uuid);
});
$answer["success"] = 1;
$answer['success'] = 1;
} catch (PDOException $e) {
$answer["success"] = 0;
$answer["message"] = "Database error, please try again";
$answer['success'] = 0;
$answer['message'] = 'Database error, please try again';
http_response_code(500);
} catch (Exception $e) {
$answer["success"] = 0;
$answer["message"] = $e->getMessage();
$answer['success'] = 0;
$answer['message'] = $e->getMessage();
http_response_code(400);
}
+9 -129
View File
@@ -1,145 +1,25 @@
<?php
<?php
session_start();
require '../../../assets/utils/db_auth.php';
require '../../../assets/utils/classes/StockManager.php';
require '../../../assets/utils/classes/WarehouseManager.php';
try {
dbTransaction($pdo2, function($pdo) use ($data, $company_id, $logging, $uuid) {
$id = (int)$data['id'];
$warehouse_id = (int)$data["warehouse"];
$whMgmt = new WarehouseManager($pdo, $company_id);
$warehouse = $whMgmt->getStockContext($warehouse_id, $id);
$row = $warehouse['row'] ?? [];
$table = $warehouse['table'];
// Append this edit/create event to td_stock_<wh>.log (stock record history)
$table_log = json_decode($row['log'] ?? '[]', true);
$table_log[] = $logging;
if ($id > 0) {
// UPDATE existing stock_out record
$sql = "UPDATE `$table` SET
`contact_id` = :contact_id,
`description` = :description,
`log` = :log
WHERE id = :id AND company_id = :company_id";
$sth = $pdo->prepare($sql);
$sth->execute([
":id" => $id,
":company_id" => $company_id,
":contact_id" => (int)$data["contact_id"],
":description" => $data["description"],
":log" => json_encode($table_log),
]);
} else {
// Look up the source stock record via the rack.
// Under 1:1, the rack points to exactly one td_stock_<wh> row.
$source_stock = $whMgmt->getRackStock(
$warehouse_id,
$data["zone"],
$data["aisle"],
$data["rack"]
);
if (!$source_stock) {
throw new Exception(
"Rack {$data['zone']}-{$data['aisle']}-{$data['rack']} is empty — nothing to take out."
);
}
// Defensive checks — UI cascade should already prevent mismatches
if ($source_stock['product_sku'] !== $data["product_sku"]) {
throw new Exception(
"Rack holds {$source_stock['product_sku']}, not {$data['product_sku']}."
);
}
// If user selected a specific lot, verify it matches the rack's stock
if (!empty($data["lot_number"]) && $source_stock['lot_number'] !== $data["lot_number"]) {
throw new Exception(
"Rack holds lot '{$source_stock['lot_number']}', not '{$data['lot_number']}'."
);
}
// If user selected a specific serial, verify it matches the rack's stock
if (!empty($data["serial_number"]) && $source_stock['serial_number'] !== $data["serial_number"]) {
throw new Exception(
"Rack holds serial '{$source_stock['serial_number']}', not '{$data['serial_number']}'."
);
}
// Quantity comes from the source stock_in row, NOT user input.
// Under 1:1, taking from this rack = taking the full batch.
$quantity = (int) $source_stock['in'];
$ref_id = (int) $source_stock['id'];
$lot_number = $source_stock['lot_number'] ?? null;
$serial_number = $source_stock['serial_number'] ?? null;
// INSERT new stock_out record.
// lot_number and serial_number carried from source stock_in row.
// ref_id links this stock_out row back to the stock_in batch it consumed.
$sql = "INSERT INTO `$table`
(uuid, company_id, `date`, product_sku, `out`, zone, aisle, rack,
contact_id, `description`, `log`, `type`, ref_id, lot_number, serial_number)
VALUES
(:uuid, :company_id, :date, :product_sku, :quantity, :zone, :aisle, :rack,
:contact_id, :description, :log, 'out', :ref_id, :lot_number, :serial_number)";
$sth = $pdo->prepare($sql);
$sth->execute([
":uuid" => $uuid,
":company_id" => $company_id,
":date" => date('Y-m-d H:i:s'),
":product_sku" => $data["product_sku"],
":quantity" => $quantity,
":zone" => $data["zone"],
":aisle" => $data["aisle"],
":rack" => $data["rack"],
":contact_id" => (int)$data["contact_id"],
":description" => $data["description"],
":log" => json_encode($table_log),
":ref_id" => $ref_id,
":lot_number" => $lot_number,
":serial_number" => $serial_number,
]);
// Release the rack — stock is now gone from this location.
$whMgmt->releaseRack(
$warehouse_id,
$data["zone"], $data["aisle"], $data["rack"]
);
// Balance: total_out increases by delta.
// INSERT: $row['out'] = 0 → delta = full quantity
// UPDATE: $row['out'] = old → delta = new - old
$whMgmt->adjustBalance(
'out',
$warehouse_id,
$data["product_sku"],
$row['out'] ?? 0,
$quantity
);
}
$stock = new StockManager($pdo, $company_id);
$stock->saveStockOut($data, $logging, $uuid);
});
$answer["success"] = 1;
$answer['success'] = 1;
} catch (PDOException $e) {
$answer["success"] = 0;
$answer["message"] = "Database error, please try again";
$answer['success'] = 0;
$answer['message'] = 'Database error, please try again';
http_response_code(500);
} catch (Exception $e) {
$answer["success"] = 0;
$answer["message"] = $e->getMessage();
$answer['success'] = 0;
$answer['message'] = $e->getMessage();
http_response_code(400);
}
+9 -223
View File
@@ -1,239 +1,25 @@
<?php
<?php
session_start();
require '../../../assets/utils/db_auth.php';
require '../../../assets/utils/classes/StockManager.php';
require '../../../assets/utils/classes/WarehouseManager.php';
try {
dbTransaction($pdo2, function($pdo) use ($data, $company_id, $logging, $uuid) {
$id = (int)($data['id'] ?? 0);
$whMgmt = new WarehouseManager($pdo, $company_id);
// Metadata that's editable on BOTH create and edit
$contact_id = (int)($data['contact_id'] ?? 0);
$description = ($data['description'] ?? '');
// ══════════════════════════════════════════════════════════
// EDIT FLOW
// Physical state (racks, balances, quantities) is frozen.
// Only metadata (contact, description) can change.
// ══════════════════════════════════════════════════════════
if ($id > 0) {
// Find the "from" row that's being edited.
// We look it up across all td_stock_<wh> via warehouse_from
// since $id alone isn't enough (it's per-table).
$from_warehouse = (int)($data['warehouse_from'] ?? 0);
$to_warehouse = (int)($data['warehouse_to'] ?? 0);
$from_ctx = $whMgmt->getStockContext($from_warehouse, $id);
$from_table = $from_ctx['table'];
$from_row = $from_ctx['row'];
if (!$from_row) {
throw new Exception("Transfer record not found.");
}
// Find the paired "to" row via ref_id stored on the "from" row
$to_id = (int) $from_row['ref_id'];
$to_ctx = $whMgmt->getStockContext($to_warehouse, $to_id);
$to_table = $to_ctx['table'];
$to_row = $to_ctx['row'];
if (!$to_row) {
throw new Exception("Paired destination record missing — data integrity issue.");
}
// Append this edit to both rows' logs
$from_log = json_decode($from_row['log'] ?? '[]', true) ?: [];
$from_log[] = $logging;
$to_log = json_decode($to_row['log'] ?? '[]', true) ?: [];
$to_log[] = $logging;
// UPDATE "from" row — only metadata fields
$sql = "UPDATE `$from_table` SET
contact_id = :contact_id,
description = :description,
`log` = :log
WHERE id = :id AND company_id = :company_id";
$pdo->prepare($sql)->execute([
":contact_id" => $contact_id,
":description" => $description,
":log" => json_encode($from_log),
":id" => $id,
":company_id" => $company_id,
]);
// UPDATE "to" row — same metadata
$sql = "UPDATE `$to_table` SET
contact_id = :contact_id,
description = :description,
`log` = :log
WHERE id = :id AND company_id = :company_id";
$pdo->prepare($sql)->execute([
":contact_id" => $contact_id,
":description" => $description,
":log" => json_encode($to_log),
":id" => $to_id,
":company_id" => $company_id,
]);
// Done — no rack ops, no balance changes on edit.
return;
}
// ══════════════════════════════════════════════════════════
// CREATE FLOW (unchanged from before)
// ══════════════════════════════════════════════════════════
// Gather source + destination parameters
$from_warehouse = (int)$data['warehouse_from'];
$from_zone = $data['zone_from'];
$from_aisle = $data['aisle_from'];
$from_rack = $data['rack_from'];
$to_warehouse = (int)$data['warehouse_to'];
$to_zone = $data['zone_to'];
$to_aisle = $data['aisle_to'];
$to_rack = $data['rack_to'];
// Validate source rack has stock of the expected SKU.
// Quantity comes from here — NOT user input.
$source_stock = $whMgmt->getRackStock(
$from_warehouse, $from_zone, $from_aisle, $from_rack
);
if (!$source_stock) {
throw new Exception(
"Source rack {$from_zone}-{$from_aisle}-{$from_rack} is empty."
);
}
if ($source_stock['product_sku'] !== $data["product_sku"]) {
throw new Exception(
"Source rack holds {$source_stock['product_sku']}, not {$data['product_sku']}."
);
}
// Defensive lot/serial checks
if (!empty($data["lot_number"]) && $source_stock['lot_number'] !== $data["lot_number"]) {
throw new Exception(
"Source rack holds lot '{$source_stock['lot_number']}', not '{$data['lot_number']}'."
);
}
if (!empty($data["serial_number"]) && $source_stock['serial_number'] !== $data["serial_number"]) {
throw new Exception(
"Source rack holds serial '{$source_stock['serial_number']}', not '{$data['serial_number']}'."
);
}
$quantity = (int) $source_stock['in'];
$lot_number = $source_stock['lot_number'] ?? null;
$serial_number = $source_stock['serial_number'] ?? null;
// Resolve both td_stock_<wh> tables
$from_ctx = $whMgmt->getStockContext($from_warehouse, 0);
$to_ctx = $whMgmt->getStockContext($to_warehouse, 0);
$from_table = $from_ctx['table'];
$to_table = $to_ctx['table'];
// Share UUID across both paired rows for easy grouping
$transfer_uuid = $uuid;
// INSERT "from" row (outbound from source warehouse)
$table_log = [$logging];
$sql = "INSERT INTO `$from_table`
(uuid, company_id, `date`, product_sku, `out`,
ref_warehouse, zone, aisle, rack,
contact_id, `description`, `log`, `type`, lot_number, serial_number)
VALUES
(:uuid, :company_id, :date, :product_sku, :quantity,
:ref_warehouse, :zone, :aisle, :rack,
:contact_id, :description, :log, 'transfer', :lot_number, :serial_number)";
$pdo->prepare($sql)->execute([
":uuid" => $transfer_uuid,
":company_id" => $company_id,
":date" => date('Y-m-d H:i:s'),
":product_sku" => $data["product_sku"],
":quantity" => $quantity,
":ref_warehouse" => $to_warehouse,
":zone" => $from_zone,
":aisle" => $from_aisle,
":rack" => $from_rack,
":contact_id" => $contact_id,
":description" => $description,
":log" => json_encode($table_log),
":lot_number" => $lot_number,
":serial_number" => $serial_number,
]);
$from_stock_id = (int) $pdo->lastInsertId();
// INSERT "to" row (inbound at destination warehouse)
$sql = "INSERT INTO `$to_table`
(uuid, company_id, `date`, product_sku, `in`,
ref_warehouse, ref_id, zone, aisle, rack,
contact_id, `description`, `log`, `type`, lot_number, serial_number)
VALUES
(:uuid, :company_id, :date, :product_sku, :quantity,
:ref_warehouse, :ref_id, :zone, :aisle, :rack,
:contact_id, :description, :log, 'transfer', :lot_number, :serial_number)";
$pdo->prepare($sql)->execute([
":uuid" => $transfer_uuid,
":company_id" => $company_id,
":date" => date('Y-m-d H:i:s'),
":product_sku" => $data["product_sku"],
":quantity" => $quantity,
":ref_warehouse" => $from_warehouse,
":ref_id" => $from_stock_id,
":zone" => $to_zone,
":aisle" => $to_aisle,
":rack" => $to_rack,
":contact_id" => $contact_id,
":description" => $description,
":log" => json_encode($table_log),
":lot_number" => $lot_number,
":serial_number" => $serial_number,
]);
$to_stock_id = (int) $pdo->lastInsertId();
// Back-fill the "from" row's ref_id now that we have to_stock_id
// so both rows point at each other
$sql = "UPDATE `$from_table`
SET ref_id = :ref_id
WHERE id = :id AND company_id = :company_id";
$pdo->prepare($sql)->execute([
":ref_id" => $to_stock_id,
":id" => $from_stock_id,
":company_id" => $company_id,
]);
// Move rack state: release source, occupy destination
// md_rack.log gets 'release' + 'occupy' entries automatically
$whMgmt->releaseRack($from_warehouse, $from_zone, $from_aisle, $from_rack);
$whMgmt->occupyRack(
$to_warehouse, $to_zone, $to_aisle, $to_rack,
$data["product_sku"], $to_stock_id
);
// Balance adjustments on both warehouses
$whMgmt->adjustBalance('out', $from_warehouse, $data["product_sku"], 0, $quantity);
$whMgmt->adjustBalance('in', $to_warehouse, $data["product_sku"], 0, $quantity);
$stock = new StockManager($pdo, $company_id);
$stock->saveStockTransfer($data, $logging, $uuid);
});
$answer["success"] = 1;
$answer['success'] = 1;
} catch (PDOException $e) {
$answer["success"] = 0;
$answer["message"] = "Database error, please try again";
$answer['success'] = 0;
$answer['message'] = 'Database error, please try again';
http_response_code(500);
} catch (Exception $e) {
$answer["success"] = 0;
$answer["message"] = $e->getMessage();
$answer['success'] = 0;
$answer['message'] = $e->getMessage();
http_response_code(400);
}
+5 -20
View File
@@ -1,25 +1,10 @@
<?php
<?php
session_start();
require '../../../assets/utils/db_auth.php';
require '../../../assets/utils/classes/ProductManager.php';
$sql = "SELECT *
FROM md_product
WHERE
company_id = :company_id and
sku like :keyword
LIMIT 50
";
$sth = $pdo2->prepare($sql);
$sth->execute([
":company_id" => $company_id,
":keyword" => '%'.$data["keyword"].'%'
]);
db_check($sth, $answer);
$result = $sth->fetchAll(PDO::FETCH_ASSOC);
$answer["result"] = $result;
$answer["success"] = 1;
$product = new ProductManager($pdo2, $company_id);
$answer['result'] = $product->searchProduct($data['keyword'] ?? '');
$answer['success'] = 1;
exit(json_encode($answer));
?>
+2 -48
View File
@@ -3,8 +3,6 @@
require '../../../assets/utils/db_auth.php';
require '../../../assets/utils/classes/WarehouseManager.php';
// Returns active lots for a given product_sku
// "Active" = EXISTS in an occupied md_rack (product_sku IS NOT NULL)
$product_sku = $data['product_sku'] ?? '';
if (empty($product_sku)) {
@@ -13,52 +11,8 @@
exit(json_encode($answer));
}
// Discover all warehouses
$sth = $pdo2->prepare(
"SELECT id, warehouse_name FROM md_warehouse
WHERE company_id = :company_id AND status = 1"
);
$sth->execute([':company_id' => $company_id]);
$warehouses = $sth->fetchAll(PDO::FETCH_ASSOC);
$active_lots = [];
foreach ($warehouses as $wh) {
$safe = preg_replace('/[^a-zA-Z0-9_]/', '', $wh['warehouse_name']);
$table = "td_stock_{$safe}";
$sth = $pdo2->prepare(
"SELECT DISTINCT s.lot_number, l.expiry_date
FROM `{$table}` s
LEFT JOIN md_lot l
ON l.company_id = s.company_id
AND l.product_sku = s.product_sku
AND l.lot_number = s.lot_number
WHERE s.company_id = :company_id
AND s.product_sku = :product_sku
AND s.type = 'in'
AND s.lot_number IS NOT NULL
AND EXISTS (
SELECT 1 FROM md_rack r
WHERE r.company_id = s.company_id
AND r.td_stock_id = s.id
AND r.product_sku IS NOT NULL
)"
);
$sth->execute([
':company_id' => $company_id,
':product_sku' => $product_sku,
]);
foreach ($sth->fetchAll(PDO::FETCH_ASSOC) as $row) {
$key = $row['lot_number'];
if (!isset($active_lots[$key])) {
$active_lots[$key] = $row;
}
}
}
$answer['output'] = array_values($active_lots);
$wh = new WarehouseManager($pdo2, $company_id);
$answer['output'] = $wh->getActiveLots($product_sku);
$answer['success'] = 1;
exit(json_encode($answer));
?>
+3 -49
View File
@@ -1,11 +1,9 @@
<?php
session_start();
require '../../../assets/utils/db_auth.php';
require '../../../assets/utils/classes/WarehouseManager.php';
// Returns active serials for a given product_sku + optional lot_number
// "Active" = EXISTS in an occupied md_rack
$product_sku = $data['product_sku'] ?? '';
$lot_number = $data['lot_number'] ?? '';
if (empty($product_sku)) {
$answer['output'] = [];
@@ -13,52 +11,8 @@
exit(json_encode($answer));
}
// Discover all warehouses
$sth = $pdo2->prepare(
"SELECT id, warehouse_name FROM md_warehouse
WHERE company_id = :company_id AND status = 1"
);
$sth->execute([':company_id' => $company_id]);
$warehouses = $sth->fetchAll(PDO::FETCH_ASSOC);
$active_serials = [];
foreach ($warehouses as $wh) {
$safe = preg_replace('/[^a-zA-Z0-9_]/', '', $wh['warehouse_name']);
$table = "td_stock_{$safe}";
$sql = "SELECT DISTINCT s.serial_number
FROM `{$table}` s
WHERE s.company_id = :company_id
AND s.product_sku = :product_sku
AND s.type = 'in'
AND s.serial_number IS NOT NULL
AND EXISTS (
SELECT 1 FROM md_rack r
WHERE r.company_id = s.company_id
AND r.td_stock_id = s.id
AND r.product_sku IS NOT NULL
)";
$params = [
':company_id' => $company_id,
':product_sku' => $product_sku,
];
if (!empty($lot_number)) {
$sql .= " AND s.lot_number = :lot_number";
$params[':lot_number'] = $lot_number;
}
$sth = $pdo2->prepare($sql);
$sth->execute($params);
foreach ($sth->fetchAll(PDO::FETCH_ASSOC) as $row) {
$active_serials[$row['serial_number']] = $row['serial_number'];
}
}
$answer['output'] = array_values($active_serials);
$wh = new WarehouseManager($pdo2, $company_id);
$answer['output'] = $wh->getActiveSerials($product_sku, $data['lot_number'] ?? '');
$answer['success'] = 1;
exit(json_encode($answer));
?>
+4 -22
View File
@@ -6,15 +6,15 @@
$type = $data['type'] ?? '';
$whMgmt = new WarehouseManager($pdo2, $company_id);
// Resolve warehouse and zone
$warehouse = (int)($data['warehouse'] ?? 0);
$zone = $data['zone'] ?? '';
if (isset($data['warehouse_from']) && $type === 'from') { $warehouse = (int)$data['warehouse_from']; $zone = $data['zone_from'] ?? $zone; }
if (isset($data['warehouse_to']) && $type === 'to') { $warehouse = (int)$data['warehouse_to']; $zone = $data['zone_to'] ?? $zone; }
if ($type === 'to') {
if (!empty($data['id'])) {
$output = $whMgmt->getAislesAll($warehouse, $zone);
} elseif ($type === 'to') {
$output = $whMgmt->getAislesIn($warehouse, $zone);
} elseif ($type === 'from') {
$output = $whMgmt->getAislesOut(
$warehouse,
@@ -23,26 +23,8 @@
$data['lot_number'] ?? null,
$data['serial_number'] ?? null
);
} else {
$sth = $pdo2->prepare(
"SELECT DISTINCT aisle FROM md_rack
WHERE company_id = :company_id AND warehouse = :warehouse AND zone = :zone
ORDER BY CAST(aisle AS UNSIGNED), aisle"
);
$sth->execute([':company_id' => $company_id, ':warehouse' => $warehouse, ':zone' => $zone]);
$output = array_column($sth->fetchAll(PDO::FETCH_ASSOC), 'aisle');
}
// Edit mode
if (!empty($data['id'])) {
$sth = $pdo2->prepare(
"SELECT DISTINCT aisle FROM md_rack
WHERE company_id = :company_id AND warehouse = :warehouse AND zone = :zone
ORDER BY CAST(aisle AS UNSIGNED), aisle"
);
$sth->execute([':company_id' => $company_id, ':warehouse' => $warehouse, ':zone' => $zone]);
$output = array_column($sth->fetchAll(PDO::FETCH_ASSOC), 'aisle');
$output = $whMgmt->getAislesAll($warehouse, $zone);
}
$answer['output'] = $output;
+8 -21
View File
@@ -1,26 +1,13 @@
<?php
<?php
session_start();
require '../../../assets/utils/db_auth.php';
require '../../../assets/utils/classes/WarehouseManager.php';
$sql = "SELECT *
FROM md_lot
WHERE
company_id = :company_id AND
product_sku = :product_sku AND
lot_number LIKE :keyword
ORDER BY lot_number ASC
LIMIT 50";
$sth = $pdo2->prepare($sql);
$sth->execute([
":company_id" => $company_id,
":product_sku" => $data["product_sku"] ?? '',
":keyword" => '%'.$data["keyword"].'%'
]);
db_check($sth, $answer);
$answer["result"] = $sth->fetchAll(PDO::FETCH_ASSOC);
$answer["success"] = 1;
$wh = new WarehouseManager($pdo2, $company_id);
$answer['result'] = $wh->getLotList(
$data['product_sku'] ?? '',
$data['keyword'] ?? ''
);
$answer['success'] = 1;
exit(json_encode($answer));
?>
+4 -22
View File
@@ -6,16 +6,16 @@
$type = $data['type'] ?? '';
$whMgmt = new WarehouseManager($pdo2, $company_id);
// Resolve warehouse, zone and aisle
$warehouse = (int)($data['warehouse'] ?? 0);
$zone = $data['zone'] ?? '';
$aisle = $data['aisle'] ?? '';
if (isset($data['warehouse_from']) && $type === 'from') { $warehouse = (int)$data['warehouse_from']; $zone = $data['zone_from'] ?? $zone; $aisle = $data['aisle_from'] ?? $aisle; }
if (isset($data['warehouse_to']) && $type === 'to') { $warehouse = (int)$data['warehouse_to']; $zone = $data['zone_to'] ?? $zone; $aisle = $data['aisle_to'] ?? $aisle; }
if ($type === 'to') {
if (!empty($data['id'])) {
$output = $whMgmt->getRacksAll($warehouse, $zone, $aisle);
} elseif ($type === 'to') {
$output = $whMgmt->getRacksIn($warehouse, $zone, $aisle);
} elseif ($type === 'from') {
$output = $whMgmt->getRacksOut(
$warehouse,
@@ -25,26 +25,8 @@
$data['lot_number'] ?? null,
$data['serial_number'] ?? null
);
} else {
$sth = $pdo2->prepare(
"SELECT DISTINCT rack FROM md_rack
WHERE company_id = :company_id AND warehouse = :warehouse AND zone = :zone AND aisle = :aisle
ORDER BY CAST(rack AS UNSIGNED), rack"
);
$sth->execute([':company_id' => $company_id, ':warehouse' => $warehouse, ':zone' => $zone, ':aisle' => $aisle]);
$output = array_column($sth->fetchAll(PDO::FETCH_ASSOC), 'rack');
}
// Edit mode
if (!empty($data['id'])) {
$sth = $pdo2->prepare(
"SELECT DISTINCT rack FROM md_rack
WHERE company_id = :company_id AND warehouse = :warehouse AND zone = :zone AND aisle = :aisle
ORDER BY CAST(rack AS UNSIGNED), rack"
);
$sth->execute([':company_id' => $company_id, ':warehouse' => $warehouse, ':zone' => $zone, ':aisle' => $aisle]);
$output = array_column($sth->fetchAll(PDO::FETCH_ASSOC), 'rack');
$output = $whMgmt->getRacksAll($warehouse, $zone, $aisle);
}
$answer['output'] = $output;
+5 -50
View File
@@ -1,55 +1,10 @@
<?php
<?php
session_start();
require '../../../assets/utils/db_auth.php';
require '../../../assets/utils/classes/StockManager.php';
$id = (int)$data['id'];
$warehouse = (int)$data["wh"];
// get warehouse name
$sql = "SELECT warehouse_name
FROM md_warehouse
WHERE
company_id = :company_id and
id = :warehouse
";
$sth = $pdo2->prepare($sql);
$sth->execute([
":company_id" => $company_id,
":warehouse" => $warehouse
]);
db_check($sth, $answer);
$warehouse = $sth->fetchColumn();
$table = "td_stock_" . $warehouse;
$sql = "SELECT a.* , a.in as quantity, b.contact_name, c.product_name, d.expiry_date
FROM `{$table}` a
LEFT JOIN md_contact b ON
a.company_id = b.company_id and
a.contact_id = b.id
LEFT JOIN md_product c ON
a.company_id = c.company_id and
a.product_sku = c.sku
LEFT JOIN md_lot d ON
a.company_id = d.company_id and
a.product_sku = d.product_sku and
a.lot_number = d.lot_number
WHERE
a.company_id = :company_id and
a.id = :id ";
$sth = $pdo2->prepare($sql);
$sth->execute([
":company_id" => $company_id,
":id" => $id
]);
db_check($sth, $answer);
$answer["output"] = $sth->fetch(PDO::FETCH_ASSOC);
$answer["success"] = 1;
$stock = new StockManager($pdo2, $company_id);
$answer['output'] = $stock->getStockInById((int)$data['wh'], (int)$data['id']);
$answer['success'] = 1;
exit(json_encode($answer));
?>
+5 -46
View File
@@ -1,51 +1,10 @@
<?php
<?php
session_start();
require '../../../assets/utils/db_auth.php';
require '../../../assets/utils/classes/StockManager.php';
$id = (int)$data['id'];
$warehouse = (int)$data["wh"];
// get warehouse name
$sql = "SELECT warehouse_name
FROM md_warehouse
WHERE
company_id = :company_id and
id = :warehouse
";
$sth = $pdo2->prepare($sql);
$sth->execute([
":company_id" => $company_id,
":warehouse" => $warehouse
]);
db_check($sth, $answer);
$warehouse = $sth->fetchColumn();
$table = "td_stock_" . $warehouse;
$sql = "SELECT a.* , a.out as quantity, b.contact_name, c.product_name
FROM `{$table}` a
LEFT JOIN md_contact b ON
a.company_id = b.company_id and
a.contact_id = b.id
LEFT JOIN md_product c ON
a.company_id = c.company_id and
a.product_sku = c.sku
WHERE
a.company_id = :company_id and
a.id = :id ";
$sth = $pdo2->prepare($sql);
$sth->execute([
":company_id" => $company_id,
":id" => $id
]);
db_check($sth, $answer);
$answer["output"] = $sth->fetch(PDO::FETCH_ASSOC);
$answer["success"] = 1;
$stock = new StockManager($pdo2, $company_id);
$answer['output'] = $stock->getStockOutById((int)$data['wh'], (int)$data['id']);
$answer['success'] = 1;
exit(json_encode($answer));
?>
+5 -96
View File
@@ -1,101 +1,10 @@
<?php
<?php
session_start();
require '../../../assets/utils/db_auth.php';
require '../../../assets/utils/classes/StockManager.php';
$id = (int)$data['id'];
$warehouse = (int)$data["wh"];
/**
* FROM warehouse, zone, aisle, rack, product name, quantity, contact name
*/
// get warehouse name
$sql = "SELECT warehouse_name
FROM md_warehouse
WHERE
company_id = :company_id and
id = :warehouse
";
$sth = $pdo2->prepare($sql);
$sth->execute([
":company_id" => $company_id,
":warehouse" => $warehouse
]);
db_check($sth, $answer);
$warehouse = $sth->fetchColumn();
$table = "td_stock_" . $warehouse;
$sql = "SELECT a.* , a.out as quantity, b.contact_name, c.product_name
FROM `{$table}` a
LEFT JOIN md_contact b ON
a.company_id = b.company_id and
a.contact_id = b.id
LEFT JOIN md_product c ON
a.company_id = c.company_id and
a.product_sku = c.sku
WHERE
a.company_id = :company_id and
a.id = :id ";
$sth = $pdo2->prepare($sql);
$sth->execute([
":company_id" => $company_id,
":id" => $id
]);
db_check($sth, $answer);
$output = $sth->fetch(PDO::FETCH_ASSOC);
/**
* TO warehouse, zone, aisle, rack, product name, quantity, contact name
*/
// get warehouse name
$sql = "SELECT warehouse_name
FROM md_warehouse
WHERE
company_id = :company_id and
id = :warehouse
";
$sth = $pdo2->prepare($sql);
$sth->execute([
":company_id" => $company_id,
":warehouse" => $output["ref_warehouse"]
]);
db_check($sth, $answer);
$warehouse = $sth->fetchColumn();
$table = "td_stock_" . $warehouse;
$sql = "SELECT a.* , a.in as quantity, b.contact_name, c.product_name
FROM `{$table}` a
LEFT JOIN md_contact b ON
a.company_id = b.company_id and
a.contact_id = b.id
LEFT JOIN md_product c ON
a.company_id = b.company_id and
a.product_sku = c.sku
WHERE
a.company_id = :company_id and
a.uuid = :id ";
$sth = $pdo2->prepare($sql);
$sth->execute([
":company_id" => $company_id,
":id" => $output["uuid"]
]);
db_check($sth, $answer);
$output["ref"] = $sth->fetch(PDO::FETCH_ASSOC);
$answer["output"] = $output;
$answer["success"] = 1;
$stock = new StockManager($pdo2, $company_id);
$answer['output'] = $stock->getTransferById((int)$data['wh'], (int)$data['id']);
$answer['success'] = 1;
exit(json_encode($answer));
?>
+9 -41
View File
@@ -1,46 +1,14 @@
<?php
<?php
session_start();
require '../../../assets/utils/db_auth.php';
require '../../../assets/utils/classes/WarehouseManager.php';
// Build filter + params based on operation type.
// Filter warehouses by occupancy:
// 'to' = destination → only warehouses with at least one empty rack
// 'from' = source → only warehouses that hold the specific product SKU
// other = all warehouses (for read-only listings, dashboards, etc.)
$product_sku_filter = "";
$params = [":company_id" => $company_id];
if ($data["type"] == "to") {
$product_sku_filter = "AND r.product_sku IS NULL";
} elseif ($data["type"] == "from") {
$product_sku_filter = "AND r.product_sku = :product_sku";
$params[":product_sku"] = $data["product_sku"];
}
if(!empty($data["id"])){
$product_sku_filter = "";
unset($params[":product_sku"]);
}
// Only list warehouses that have racks matching the occupancy filter.
// EXISTS is efficient here — it stops searching as soon as one match is found.
$sql = "SELECT w.id, w.warehouse_name
FROM md_warehouse w
WHERE w.company_id = :company_id
AND EXISTS (
SELECT 1 FROM md_rack r
WHERE r.company_id = w.company_id
AND r.warehouse = w.id
$product_sku_filter
)
ORDER BY w.warehouse_name";
$sth = $pdo2->prepare($sql);
$sth->execute($params);
db_check($sth, $answer);
$answer["output"] = $sth->fetchAll(PDO::FETCH_ASSOC);
$answer["success"] = 1;
$wh = new WarehouseManager($pdo2, $company_id);
$answer['output'] = $wh->getWarehouseList(
$data['type'] ?? '',
$data['product_sku'] ?? '',
(int)($data['id'] ?? 0)
);
$answer['success'] = 1;
exit(json_encode($answer));
?>
+4 -25
View File
@@ -6,44 +6,23 @@
$type = $data['type'] ?? '';
$whMgmt = new WarehouseManager($pdo2, $company_id);
// Resolve warehouse — supports warehouse, warehouse_from, warehouse_to
$warehouse = (int)($data['warehouse'] ?? 0);
if (isset($data['warehouse_from']) && $type === 'from') $warehouse = (int)$data['warehouse_from'];
if (isset($data['warehouse_to']) && $type === 'to') $warehouse = (int)$data['warehouse_to'];
if ($type === 'to') {
// Stock_in destination — empty racks only
if (!empty($data['id'])) {
$output = $whMgmt->getZonesAll($warehouse);
} elseif ($type === 'to') {
$output = $whMgmt->getZonesIn($warehouse);
} elseif ($type === 'from') {
// Stock_out source — filter by sku + lot + serial
$output = $whMgmt->getZonesOut(
$warehouse,
$data['product_sku'] ?? '',
$data['lot_number'] ?? null,
$data['serial_number'] ?? null
);
} else {
// Admin / read-only — all zones in the warehouse
$sth = $pdo2->prepare(
"SELECT DISTINCT zone FROM md_rack
WHERE company_id = :company_id AND warehouse = :warehouse
ORDER BY CAST(zone AS UNSIGNED), zone"
);
$sth->execute([':company_id' => $company_id, ':warehouse' => $warehouse]);
$output = $sth->fetchAll(PDO::FETCH_ASSOC);
}
// Edit mode — return all zones regardless of occupancy
if (!empty($data['id'])) {
$sth = $pdo2->prepare(
"SELECT DISTINCT zone FROM md_rack
WHERE company_id = :company_id AND warehouse = :warehouse
ORDER BY CAST(zone AS UNSIGNED), zone"
);
$sth->execute([':company_id' => $company_id, ':warehouse' => $warehouse]);
$output = $sth->fetchAll(PDO::FETCH_ASSOC);
$output = $whMgmt->getZonesAll($warehouse);
}
$answer['output'] = $output;
+5 -48
View File
@@ -1,53 +1,10 @@
<?php
<?php
session_start();
require '../../../assets/utils/db_auth.php';
require '../../../assets/utils/classes/StockManager.php';
$id = (int)$data['id'];
$warehouse = (int)$data["warehouse"];
// get warehouse name
$sql = "SELECT warehouse_name
FROM md_warehouse
WHERE
company_id = :company_id and
id = :warehouse
";
$sth = $pdo2->prepare($sql);
$sth->execute([
":company_id" => $company_id,
":warehouse" => $warehouse
]);
db_check($sth, $answer);
$warehouse = $sth->fetchColumn();
if(!$warehouse) {
$answer["message"] = "Warehouse not found";
exit(json_encode($answer));
}
$table = "td_stock_" . $warehouse;
$sql = "SELECT a.* , ROUND(a.in,2) as quantity ,
b.product_name
FROM `{$table}` a
LEFT JOIN md_product b ON
a.company_id = b.company_id and
a.product_sku = b.sku
WHERE
a.company_id = {$company_id} and
a.type = 'in'
ORDER BY a.date DESC";
$sth = $pdo2->prepare($sql);
$sth->execute([
":company_id" => $company_id
]);
db_check($sth, $answer);
$answer["output"] = $sth->fetchAll(PDO::FETCH_ASSOC);
$answer["success"] = 1;
$stock = new StockManager($pdo2, $company_id);
$answer['output'] = $stock->getStockList((int)$data['warehouse'], 'in');
$answer['success'] = 1;
exit(json_encode($answer));
?>
+5 -48
View File
@@ -1,53 +1,10 @@
<?php
<?php
session_start();
require '../../../assets/utils/db_auth.php';
require '../../../assets/utils/classes/StockManager.php';
$id = (int)$data['id'];
$warehouse = (int)$data["warehouse"];
// get warehouse name
$sql = "SELECT warehouse_name
FROM md_warehouse
WHERE
company_id = :company_id and
id = :warehouse
";
$sth = $pdo2->prepare($sql);
$sth->execute([
":company_id" => $company_id,
":warehouse" => $warehouse
]);
db_check($sth, $answer);
$warehouse = $sth->fetchColumn();
if(!$warehouse) {
$answer["message"] = "Warehouse not found";
exit(json_encode($answer));
}
$table = "td_stock_" . $warehouse;
$sql = "SELECT a.* , ROUND(a.out,2) as quantity ,
b.product_name
FROM `{$table}` a
LEFT JOIN md_product b ON
a.company_id = b.company_id and
a.product_sku = b.sku
WHERE
a.company_id = {$company_id} and
a.type = 'out'
ORDER BY a.date DESC";
$sth = $pdo2->prepare($sql);
$sth->execute([
":company_id" => $company_id
]);
db_check($sth, $answer);
$answer["output"] = $sth->fetchAll(PDO::FETCH_ASSOC);
$answer["success"] = 1;
$stock = new StockManager($pdo2, $company_id);
$answer['output'] = $stock->getStockList((int)$data['warehouse'], 'out');
$answer['success'] = 1;
exit(json_encode($answer));
?>
+5 -49
View File
@@ -1,54 +1,10 @@
<?php
<?php
session_start();
require '../../../assets/utils/db_auth.php';
require '../../../assets/utils/classes/StockManager.php';
$id = (int)$data['id'];
$warehouse = (int)$data["warehouse"];
// get warehouse name
$sql = "SELECT warehouse_name
FROM md_warehouse
WHERE
company_id = :company_id and
id = :warehouse
";
$sth = $pdo2->prepare($sql);
$sth->execute([
":company_id" => $company_id,
":warehouse" => $warehouse
]);
db_check($sth, $answer);
$warehouse = $sth->fetchColumn();
if(!$warehouse) {
$answer["message"] = "Warehouse not found";
exit(json_encode($answer));
}
$table = "td_stock_" . $warehouse;
$sql = "SELECT a.* , ROUND(a.in,2) as quantity,
b.product_name
FROM `{$table}` a
LEFT JOIN md_product b ON
a.company_id = b.company_id and
a.product_sku = b.sku
WHERE
a.company_id = {$company_id} and
a.type = 'transfer' and
a.out > 0
ORDER BY a.date DESC";
$sth = $pdo2->prepare($sql);
$sth->execute([
":company_id" => $company_id
]);
db_check($sth, $answer);
$answer["output"] = $sth->fetchAll(PDO::FETCH_ASSOC);
$answer["success"] = 1;
$stock = new StockManager($pdo2, $company_id);
$answer['output'] = $stock->getStockList((int)$data['warehouse'], 'transfer');
$answer['success'] = 1;
exit(json_encode($answer));
?>
+5 -61
View File
@@ -1,66 +1,10 @@
<?php
<?php
session_start();
require '../../../assets/utils/db_auth.php';
require '../../../assets/utils/classes/WarehouseManager.php';
require '../../../assets/utils/classes/ReportManager.php';
$id = 0;
$warehouse_id = (int)$data['warehouse'];
$whMgmt = new WarehouseManager($pdo2, $company_id);
$warehouse = $whMgmt->getStockContext($warehouse_id, $id);
$table = $warehouse['table'];
// ── Last 10 stock in/out transactions ────────────────────────────────
$sql = "SELECT
a.id,
a.date,
a.product_sku,
a.`in` AS stock_in,
a.`out` AS stock_out,
a.type,
a.description,
b.product_name
FROM `{$table}` a
LEFT JOIN md_product b ON
a.company_id = b.company_id and
a.product_sku = b.sku
WHERE
a.company_id = {$company_id} and
(a.`in` > 0 OR a.`out` > 0)
ORDER BY a.date DESC, a.id DESC
LIMIT 10";
$sth = $pdo2->prepare($sql);
$sth->execute();
$rows = $sth->fetchAll(PDO::FETCH_ASSOC);
// ── Build output ─────────────────────────────────────────────────────
$activities = [];
foreach ($rows as $row) {
$is_in = (float)$row['stock_in'] > 0;
$qty = $is_in ? (float)$row['stock_in'] : (float)$row['stock_out'];
$activities[] = [
'product_name' => $row['product_name'] ?: $row['product_sku'],
'sku' => $row['product_sku'],
'direction' => $is_in ? 'in' : 'out',
'qty' => round($qty, 2),
'type' => $row['type'],
'description' => $row['description'],
'date' => $row['date'],
];
}
// ── Response ─────────────────────────────────────────────────────────
$output = [
'activities' => $activities,
];
$answer["output"] = $output;
$answer["success"] = 1;
$report = new ReportManager($pdo2, $company_id);
$answer['output'] = $report->getWarehouseActivity((int)$data['warehouse']);
$answer['success'] = 1;
exit(json_encode($answer));
?>
+5 -65
View File
@@ -1,70 +1,10 @@
<?php
<?php
session_start();
require '../../../assets/utils/db_auth.php';
require '../../../assets/utils/classes/WarehouseManager.php';
require '../../../assets/utils/classes/ReportManager.php';
$id = 0;
$warehouse_id = (int)$data['warehouse'];
$whMgmt = new WarehouseManager($pdo2, $company_id);
$warehouse = $whMgmt->getStockContext($warehouse_id, $id);
$table = $warehouse['table'];
// ── Always last 12 months ────────────────────────────────────────────
$now = new DateTime();
$start = (clone $now)->modify('-12 months')->format('Y-m-d 00:00:00');
$end = $now->format('Y-m-d 23:59:59');
// ── Query: group by month ────────────────────────────────────────────
$sql = "SELECT
DATE_FORMAT(a.date, '%b') AS `label`,
DATE_FORMAT(a.date, '%Y-%m') AS `sort_key`,
ROUND(SUM(a.`in`), 2) AS `stock_in`,
ROUND(SUM(a.`out`), 2) AS `stock_out`
FROM `{$table}` a
WHERE
a.company_id = {$company_id} and
a.date >= '{$start}' and
a.date <= '{$end}'
GROUP BY `sort_key`, `label`
ORDER BY `sort_key` ASC";
$sth = $pdo2->prepare($sql);
$sth->execute();
$rows = $sth->fetchAll(PDO::FETCH_ASSOC);
// ── Index DB results by sort_key (YYYY-MM) ──────────────────────────
$dataByMonth = [];
foreach ($rows as $row) {
$dataByMonth[$row['sort_key']] = $row;
}
// ── Always build 12 months, fill zeros where no data ────────────────
$labels = [];
$stock_in = [];
$stock_out = [];
for ($i = 11; $i >= 0; $i--) {
$dt = (clone $now)->modify("-{$i} months");
$key = $dt->format('Y-m'); // e.g. "2025-04"
$label = $dt->format('M'); // e.g. "Apr"
$labels[] = $label;
$stock_in[] = isset($dataByMonth[$key]) ? round((float)$dataByMonth[$key]['stock_in'], 2) : 0;
$stock_out[] = isset($dataByMonth[$key]) ? round((float)$dataByMonth[$key]['stock_out'], 2) : 0;
}
// ── Response ─────────────────────────────────────────────────────────
$output = [
'labels' => $labels,
'stock_in' => $stock_in,
'stock_out' => $stock_out,
];
$answer["output"] = $output;
$answer["success"] = 1;
$report = new ReportManager($pdo2, $company_id);
$answer['output'] = $report->getWarehouseStockMovement((int)$data['warehouse']);
$answer['success'] = 1;
exit(json_encode($answer));
?>
+5 -79
View File
@@ -1,84 +1,10 @@
<?php
<?php
session_start();
require '../../../assets/utils/db_auth.php';
require '../../../assets/utils/classes/WarehouseManager.php';
require '../../../assets/utils/classes/ReportManager.php';
$id = 0;
$warehouse_id = (int)$data['warehouse'];
$whMgmt = new WarehouseManager($pdo2, $company_id);
$warehouse = $whMgmt->getStockContext($warehouse_id, $id);
$table = $warehouse['table'];
// ── Always last 12 months ────────────────────────────────────────────
$now = new DateTime();
$start = (clone $now)->modify('-12 months')->format('Y-m-d 00:00:00');
$end = $now->format('Y-m-d 23:59:59');
// ── Get cumulative balance BEFORE the 12-month window ────────────────
$sql_prior = "SELECT
ROUND(COALESCE(SUM(a.`in`), 0) - COALESCE(SUM(a.`out`), 0), 2) AS `balance`
FROM `{$table}` a
WHERE
a.company_id = {$company_id} and
a.date < '{$start}'";
$sth = $pdo2->prepare($sql_prior);
$sth->execute();
$prior_balance = (float)$sth->fetchColumn();
// ── Get monthly in/out for the 12-month window ──────────────────────
$sql = "SELECT
DATE_FORMAT(a.date, '%Y-%m') AS `sort_key`,
ROUND(SUM(a.`in`), 2) AS `stock_in`,
ROUND(SUM(a.`out`), 2) AS `stock_out`
FROM `{$table}` a
WHERE
a.company_id = {$company_id} and
a.date >= '{$start}' and
a.date <= '{$end}'
GROUP BY `sort_key`
ORDER BY `sort_key` ASC";
$sth = $pdo2->prepare($sql);
$sth->execute();
$rows = $sth->fetchAll(PDO::FETCH_ASSOC);
// ── Index DB results by sort_key (YYYY-MM) ──────────────────────────
$dataByMonth = [];
foreach ($rows as $row) {
$dataByMonth[$row['sort_key']] = $row;
}
// ── Always build 12 months, accumulate running balance ──────────────
$labels = [];
$balance = [];
$running = $prior_balance;
for ($i = 11; $i >= 0; $i--) {
$dt = (clone $now)->modify("-{$i} months");
$key = $dt->format('Y-m');
$label = $dt->format('M');
$month_in = isset($dataByMonth[$key]) ? (float)$dataByMonth[$key]['stock_in'] : 0;
$month_out = isset($dataByMonth[$key]) ? (float)$dataByMonth[$key]['stock_out'] : 0;
$running += ($month_in - $month_out);
$labels[] = $label;
$balance[] = round($running, 2);
}
// ── Response ─────────────────────────────────────────────────────────
$output = [
'labels' => $labels,
'balance' => $balance,
];
$answer["output"] = $output;
$answer["success"] = 1;
$report = new ReportManager($pdo2, $company_id);
$answer['output'] = $report->getWarehouseStockTrend((int)$data['warehouse']);
$answer['success'] = 1;
exit(json_encode($answer));
?>
@@ -1,24 +1,10 @@
<?php
<?php
session_start();
require '../../../assets/utils/db_auth.php';
require '../../../assets/utils/classes/ReportManager.php';
$sql = "SELECT SUM(total_in) as total_in, SUM(total_out) as total_out, COUNT(DISTINCT warehouse_id) as total_warehouse
FROM warehouse_balance a
WHERE
a.company_id = :company_id
";
$sth = $pdo2->prepare($sql);
$sth->execute([
":company_id" => $company_id
]);
db_check($sth, $answer);
$output = $sth->fetch(PDO::FETCH_ASSOC);
$answer["output"] = $output;
$answer["success"] = 1;
$report = new ReportManager($pdo2, $company_id);
$answer['output'] = $report->getWarehouseBalanceSummary();
$answer['success'] = 1;
exit(json_encode($answer));
?>