generic API response + ICS: warehouse_balance md_rack update
This commit is contained in:
+36
-4
@@ -401,12 +401,34 @@ function ajax_request(options) {
|
|||||||
isAjaxProcessing = false;
|
isAjaxProcessing = false;
|
||||||
$.LoadingOverlay("hide");
|
$.LoadingOverlay("hide");
|
||||||
|
|
||||||
|
// Errors re-thrown from .then() — pass through
|
||||||
if (xhr instanceof Error) {
|
if (xhr instanceof Error) {
|
||||||
throw xhr;
|
throw xhr;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.error("AJAX Error:", xhr?.responseText);
|
// Extract server's error message from JSON response
|
||||||
bootbox.alert("Server error occurred.");
|
let serverMessage = xhr?.responseJSON?.message;
|
||||||
|
|
||||||
|
// Fallback: parse responseText if responseJSON isn't set
|
||||||
|
if (!serverMessage && xhr?.responseText) {
|
||||||
|
try {
|
||||||
|
serverMessage = JSON.parse(xhr.responseText)?.message;
|
||||||
|
} catch (e) {
|
||||||
|
// Response wasn't JSON — real server crash or HTML error page
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (serverMessage) {
|
||||||
|
// Known server-reported error (HTTP 4xx/5xx with JSON body)
|
||||||
|
bootbox.alert(serverMessage);
|
||||||
|
options.onError?.(xhr, serverMessage);
|
||||||
|
} else {
|
||||||
|
// Unknown error — network failure, PHP fatal, HTML error page
|
||||||
|
console.error("AJAX Error:", xhr?.status, xhr?.responseText);
|
||||||
|
bootbox.alert("Server error occurred.");
|
||||||
|
options.onError?.(xhr, null);
|
||||||
|
}
|
||||||
|
|
||||||
throw xhr;
|
throw xhr;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -422,6 +444,7 @@ function generate_pagination(table_id, total_records, records_per_page, current_
|
|||||||
// 1. Find the table and thead
|
// 1. Find the table and thead
|
||||||
const table = document.getElementById(table_id);
|
const table = document.getElementById(table_id);
|
||||||
let theme_class = '';
|
let theme_class = '';
|
||||||
|
let col_count = 1;
|
||||||
|
|
||||||
if (table) {
|
if (table) {
|
||||||
const thead = table.querySelector('thead');
|
const thead = table.querySelector('thead');
|
||||||
@@ -433,16 +456,25 @@ function generate_pagination(table_id, total_records, records_per_page, current_
|
|||||||
// 3. Convert "table-secondary" to "page-item-secondary"
|
// 3. Convert "table-secondary" to "page-item-secondary"
|
||||||
theme_class = bootstrap_theme.replace('table-', 'page-item-');
|
theme_class = bootstrap_theme.replace('table-', 'page-item-');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 4. Count <th> in the first row of thead
|
||||||
|
const header_row = thead.querySelector('tr');
|
||||||
|
if (header_row) {
|
||||||
|
col_count = header_row.querySelectorAll('th').length || 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const left_span = Math.floor(col_count / 2);
|
||||||
|
const right_span = Math.ceil(col_count / 2);
|
||||||
|
|
||||||
let pagination_html = `
|
let pagination_html = `
|
||||||
<tr>
|
<tr>
|
||||||
<td class="border-bottom-0">
|
<td colspan="${left_span}" class="border-bottom-0">
|
||||||
<span class="text-muted">Showing ${records_per_page} per page</span><br>
|
<span class="text-muted">Showing ${records_per_page} per page</span><br>
|
||||||
<strong>Total: ${total_records}</strong>
|
<strong>Total: ${total_records}</strong>
|
||||||
</td>
|
</td>
|
||||||
<td colspan="9" class="border-bottom-0">
|
<td colspan="${right_span}" class="border-bottom-0">
|
||||||
<nav aria-label="Page navigation" class="d-flex justify-content-end">
|
<nav aria-label="Page navigation" class="d-flex justify-content-end">
|
||||||
<ul class="pagination mb-0">`;
|
<ul class="pagination mb-0">`;
|
||||||
|
|
||||||
|
|||||||
@@ -97,6 +97,15 @@ class Reports
|
|||||||
return (int) $this->fetchScalar($sql);
|
return (int) $this->fetchScalar($sql);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getTotalProductInStock(): int
|
||||||
|
{
|
||||||
|
$sql = "SELECT COUNT(DISTINCT product_sku)
|
||||||
|
FROM warehouse_balance
|
||||||
|
WHERE company_id = :company_id
|
||||||
|
AND (total_in - total_out) > 0";
|
||||||
|
return (int) $this->fetchScalar($sql);
|
||||||
|
}
|
||||||
|
|
||||||
public function getLowStockCount(): int
|
public function getLowStockCount(): int
|
||||||
{
|
{
|
||||||
$products = $this->getStockBalance();
|
$products = $this->getStockBalance();
|
||||||
|
|||||||
@@ -15,9 +15,8 @@ class WarehouseManager {
|
|||||||
|
|
||||||
private $pdo;
|
private $pdo;
|
||||||
private $company_id;
|
private $company_id;
|
||||||
|
public function __construct($pdo, $company_id, $logging = null) {
|
||||||
public function __construct($pdo, $company_id) {
|
$this->pdo = $pdo;
|
||||||
$this->pdo = $pdo;
|
|
||||||
$this->company_id = $company_id;
|
$this->company_id = $company_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,20 +217,87 @@ class WarehouseManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch the stock record currently linked to a rack.
|
||||||
|
*
|
||||||
|
* Under the 1:1 model, each occupied rack points to exactly one
|
||||||
|
* td_stock_<wh> row via md_rack.td_stock_id. This method resolves
|
||||||
|
* that pointer — returning the full stock row, or null if the rack
|
||||||
|
* is empty or the link is broken.
|
||||||
|
*
|
||||||
|
* The td_stock table name is derived from the warehouse (via
|
||||||
|
* getStockContext), so callers don't need to know the naming convention.
|
||||||
|
*/
|
||||||
|
public function getRackStock($warehouse_id, $zone, $aisle, $rack): ?array {
|
||||||
|
|
||||||
|
// Get the stock pointer from md_rack
|
||||||
|
$sql = "SELECT td_stock_id FROM md_rack
|
||||||
|
WHERE company_id = :company_id
|
||||||
|
AND warehouse = :warehouse
|
||||||
|
AND zone = :zone
|
||||||
|
AND aisle = :aisle
|
||||||
|
AND rack = :rack";
|
||||||
|
|
||||||
|
$sth = $this->pdo->prepare($sql);
|
||||||
|
$sth->execute([
|
||||||
|
":company_id" => $this->company_id,
|
||||||
|
":warehouse" => $warehouse_id,
|
||||||
|
":zone" => $zone,
|
||||||
|
":aisle" => $aisle,
|
||||||
|
":rack" => $rack,
|
||||||
|
]);
|
||||||
|
$td_stock_id = $sth->fetchColumn();
|
||||||
|
|
||||||
|
// Rack is empty or doesn't exist
|
||||||
|
if (!$td_stock_id) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve the td_stock_<wh> table from the warehouse name
|
||||||
|
// and fetch the full row by ID.
|
||||||
|
$context = $this->getStockContext($warehouse_id, $td_stock_id);
|
||||||
|
return $context['row'] ?: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────
|
||||||
// Rack occupancy (md_rack.product_sku state)
|
// Rack occupancy (md_rack.product_sku + md_rack.td_stock_id state)
|
||||||
// ─────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assign a product SKU to an empty rack.
|
* Build a log JSON by appending an action entry to an existing md_rack.log.
|
||||||
*
|
* Entry captures user_id, dt (now), and login (session start) for audit context,
|
||||||
* @throws Exception if the rack is already occupied by a different SKU
|
* plus any action-specific fields passed via $extra.
|
||||||
* or if the rack doesn't exist.
|
|
||||||
*/
|
*/
|
||||||
public function occupyRack($warehouse_id, $zone, $aisle, $rack, $product_sku): void {
|
private function buildRackLog(?string $existing_log, string $action, array $extra = []): string {
|
||||||
|
|
||||||
// Lock and check the rack's current state
|
$log = json_decode($existing_log ?: '[]', true) ?: [];
|
||||||
$sql = "SELECT id, product_sku FROM md_rack
|
|
||||||
|
$log[] = array_merge([
|
||||||
|
'user_id' => $_SESSION['login_user_id'] ?? null,
|
||||||
|
'dt' => date('Y-m-d H:i:s'),
|
||||||
|
'login' => isset($_SESSION['otpTime'])
|
||||||
|
? date('Y-m-d H:i:s', $_SESSION['otpTime'])
|
||||||
|
: null,
|
||||||
|
'action' => $action,
|
||||||
|
], $extra);
|
||||||
|
|
||||||
|
return json_encode($log, JSON_UNESCAPED_UNICODE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assign a product SKU to an empty rack and link it to its stock record.
|
||||||
|
*
|
||||||
|
* The td_stock_<warehouse> table is derived from the warehouse,
|
||||||
|
* so only the row ID needs to be passed here.
|
||||||
|
*
|
||||||
|
* @throws Exception if the rack is already occupied or doesn't exist.
|
||||||
|
*/
|
||||||
|
public function occupyRack($warehouse_id, $zone, $aisle, $rack, $product_sku, $td_stock_id): void {
|
||||||
|
|
||||||
|
// Lock and fetch current state (including log for in-place append)
|
||||||
|
$sql = "SELECT id, product_sku, `log` FROM md_rack
|
||||||
WHERE company_id = :company_id
|
WHERE company_id = :company_id
|
||||||
AND warehouse = :warehouse
|
AND warehouse = :warehouse
|
||||||
AND zone = :zone
|
AND zone = :zone
|
||||||
@@ -253,37 +319,44 @@ class WarehouseManager {
|
|||||||
throw new Exception("Rack {$zone}-{$aisle}-{$rack} does not exist");
|
throw new Exception("Rack {$zone}-{$aisle}-{$rack} does not exist");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($current['product_sku'] !== null
|
if ($current['product_sku'] !== null) {
|
||||||
&& $current['product_sku'] !== $product_sku) {
|
|
||||||
throw new Exception(
|
throw new Exception(
|
||||||
"Rack {$zone}-{$aisle}-{$rack} is already occupied by {$current['product_sku']}"
|
"Rack {$zone}-{$aisle}-{$rack} is already occupied by {$current['product_sku']}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assign the SKU
|
// Single atomic UPDATE: state + log together
|
||||||
$sql = "UPDATE md_rack
|
$sql = "UPDATE md_rack
|
||||||
SET product_sku = :product_sku
|
SET product_sku = :product_sku,
|
||||||
|
td_stock_id = :td_stock_id,
|
||||||
|
`log` = :log
|
||||||
WHERE id = :id";
|
WHERE id = :id";
|
||||||
|
|
||||||
$sth = $this->pdo->prepare($sql);
|
$sth = $this->pdo->prepare($sql);
|
||||||
$sth->execute([
|
$sth->execute([
|
||||||
":product_sku" => $product_sku,
|
":product_sku" => $product_sku,
|
||||||
|
":td_stock_id" => $td_stock_id,
|
||||||
|
":log" => $this->buildRackLog($current['log'], 'occupy', [
|
||||||
|
'product_sku' => $product_sku,
|
||||||
|
'td_stock_id' => $td_stock_id,
|
||||||
|
]),
|
||||||
":id" => $current['id'],
|
":id" => $current['id'],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Release a rack (set product_sku to NULL).
|
* Release a rack (clear both SKU and stock reference together).
|
||||||
*/
|
*/
|
||||||
public function releaseRack($warehouse_id, $zone, $aisle, $rack): void {
|
public function releaseRack($warehouse_id, $zone, $aisle, $rack): void {
|
||||||
|
|
||||||
$sql = "UPDATE md_rack
|
// Fetch current state + log (needed to append release event)
|
||||||
SET product_sku = NULL
|
$sql = "SELECT id, product_sku, td_stock_id, `log` FROM md_rack
|
||||||
WHERE company_id = :company_id
|
WHERE company_id = :company_id
|
||||||
AND warehouse = :warehouse
|
AND warehouse = :warehouse
|
||||||
AND zone = :zone
|
AND zone = :zone
|
||||||
AND aisle = :aisle
|
AND aisle = :aisle
|
||||||
AND rack = :rack";
|
AND rack = :rack
|
||||||
|
FOR UPDATE";
|
||||||
|
|
||||||
$sth = $this->pdo->prepare($sql);
|
$sth = $this->pdo->prepare($sql);
|
||||||
$sth->execute([
|
$sth->execute([
|
||||||
@@ -293,12 +366,35 @@ class WarehouseManager {
|
|||||||
":aisle" => $aisle,
|
":aisle" => $aisle,
|
||||||
":rack" => $rack,
|
":rack" => $rack,
|
||||||
]);
|
]);
|
||||||
|
$current = $sth->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// Nothing to release — exit silently (idempotent behavior)
|
||||||
|
if (!$current || $current['product_sku'] === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear state + record log in one UPDATE
|
||||||
|
$sql = "UPDATE md_rack
|
||||||
|
SET product_sku = NULL,
|
||||||
|
td_stock_id = NULL,
|
||||||
|
`log` = :log
|
||||||
|
WHERE id = :id";
|
||||||
|
|
||||||
|
$sth = $this->pdo->prepare($sql);
|
||||||
|
$sth->execute([
|
||||||
|
":log" => $this->buildRackLog($current['log'], 'release', [
|
||||||
|
'product_sku' => $current['product_sku'],
|
||||||
|
'td_stock_id' => $current['td_stock_id'],
|
||||||
|
]),
|
||||||
|
":id" => $current['id'],
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Move a rack assignment from one location to another.
|
* Move a rack assignment from one location to another.
|
||||||
*
|
*
|
||||||
* Source rack must be occupied, destination rack must be empty.
|
* Source rack must be occupied, destination rack must be empty.
|
||||||
|
* Both product_sku and td_stock_id travel together to the destination.
|
||||||
* Locks both racks (lower ID first) to avoid deadlocks.
|
* Locks both racks (lower ID first) to avoid deadlocks.
|
||||||
*/
|
*/
|
||||||
public function transferRack(
|
public function transferRack(
|
||||||
@@ -307,7 +403,7 @@ class WarehouseManager {
|
|||||||
): void {
|
): void {
|
||||||
|
|
||||||
// Fetch both racks with FOR UPDATE, ordered by id to prevent deadlocks
|
// Fetch both racks with FOR UPDATE, ordered by id to prevent deadlocks
|
||||||
$sql = "SELECT id, warehouse, zone, aisle, rack, product_sku
|
$sql = "SELECT id, warehouse, zone, aisle, rack, product_sku, td_stock_id, `log`
|
||||||
FROM md_rack
|
FROM md_rack
|
||||||
WHERE company_id = :company_id
|
WHERE company_id = :company_id
|
||||||
AND (
|
AND (
|
||||||
@@ -362,17 +458,36 @@ class WarehouseManager {
|
|||||||
throw new Exception("Destination rack is already occupied");
|
throw new Exception("Destination rack is already occupied");
|
||||||
}
|
}
|
||||||
|
|
||||||
$sku = $from['product_sku'];
|
// Carry both SKU and stock reference across
|
||||||
|
$sku = $from['product_sku'];
|
||||||
|
$td_stock_id = $from['td_stock_id'];
|
||||||
|
|
||||||
// Clear source
|
// Clear source — state + log in one UPDATE
|
||||||
$sql = "UPDATE md_rack SET product_sku = NULL WHERE id = :id";
|
$sql = "UPDATE md_rack
|
||||||
$this->pdo->prepare($sql)->execute([":id" => $from['id']]);
|
SET product_sku = NULL, td_stock_id = NULL, `log` = :log
|
||||||
|
WHERE id = :id";
|
||||||
// Populate destination
|
|
||||||
$sql = "UPDATE md_rack SET product_sku = :sku WHERE id = :id";
|
|
||||||
$this->pdo->prepare($sql)->execute([
|
$this->pdo->prepare($sql)->execute([
|
||||||
":sku" => $sku,
|
":log" => $this->buildRackLog($from['log'], 'transfer_out', [
|
||||||
":id" => $to['id'],
|
'product_sku' => $sku,
|
||||||
|
'td_stock_id' => $td_stock_id,
|
||||||
|
'to_rack_id' => $to['id'],
|
||||||
|
]),
|
||||||
|
":id" => $from['id'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Populate destination — state + log in one UPDATE
|
||||||
|
$sql = "UPDATE md_rack
|
||||||
|
SET product_sku = :sku, td_stock_id = :td_stock_id, `log` = :log
|
||||||
|
WHERE id = :id";
|
||||||
|
$this->pdo->prepare($sql)->execute([
|
||||||
|
":sku" => $sku,
|
||||||
|
":td_stock_id" => $td_stock_id,
|
||||||
|
":log" => $this->buildRackLog($to['log'], 'transfer_in', [
|
||||||
|
'product_sku' => $sku,
|
||||||
|
'td_stock_id' => $td_stock_id,
|
||||||
|
'from_rack_id' => $from['id'],
|
||||||
|
]),
|
||||||
|
":id" => $to['id'],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,11 @@ require_once __DIR__."/../../config.php";
|
|||||||
require_once __DIR__."/../../dbconn.php";
|
require_once __DIR__."/../../dbconn.php";
|
||||||
require_once __DIR__."/db_helpers.php";
|
require_once __DIR__."/db_helpers.php";
|
||||||
|
|
||||||
|
// Tell the browser (and jQuery) the response body is JSON.
|
||||||
|
// Without this, responses default to text/html, which prevents
|
||||||
|
// xhr.responseJSON from being populated automatically in ajax_request().
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
if(!empty($_SESSION["login_company_id"])){
|
if(!empty($_SESSION["login_company_id"])){
|
||||||
|
|
||||||
// CSRF Validation — add right at the top of the logged-in block
|
// CSRF Validation — add right at the top of the logged-in block
|
||||||
|
|||||||
@@ -95,11 +95,13 @@
|
|||||||
if (isset($uploader)) $uploader->rollbackUploads();
|
if (isset($uploader)) $uploader->rollbackUploads();
|
||||||
$answer["success"] = 0;
|
$answer["success"] = 0;
|
||||||
$answer["message"] = "Database error, please try again";
|
$answer["message"] = "Database error, please try again";
|
||||||
|
http_response_code(500);
|
||||||
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
if (isset($uploader)) $uploader->rollbackUploads();
|
if (isset($uploader)) $uploader->rollbackUploads();
|
||||||
$answer["success"] = 0;
|
$answer["success"] = 0;
|
||||||
$answer["message"] = $e->getMessage();
|
$answer["message"] = $e->getMessage();
|
||||||
|
http_response_code(400);
|
||||||
}
|
}
|
||||||
|
|
||||||
exit(json_encode($answer));
|
exit(json_encode($answer));
|
||||||
|
|||||||
@@ -56,10 +56,12 @@
|
|||||||
} catch (PDOException $e) {
|
} catch (PDOException $e) {
|
||||||
$answer["success"] = 0;
|
$answer["success"] = 0;
|
||||||
$answer["message"] = "Database error, please try again";
|
$answer["message"] = "Database error, please try again";
|
||||||
|
http_response_code(500);
|
||||||
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
$answer["success"] = 0;
|
$answer["success"] = 0;
|
||||||
$answer["message"] = $e->getMessage();
|
$answer["message"] = $e->getMessage();
|
||||||
|
http_response_code(400);
|
||||||
}
|
}
|
||||||
|
|
||||||
exit(json_encode($answer));
|
exit(json_encode($answer));
|
||||||
|
|||||||
@@ -16,38 +16,67 @@
|
|||||||
$row = $warehouse['row'] ?? [];
|
$row = $warehouse['row'] ?? [];
|
||||||
$table = $warehouse['table'];
|
$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 = json_decode($row['log'] ?? '[]', true);
|
||||||
$table_log[] = $logging;
|
$table_log[] = $logging;
|
||||||
|
|
||||||
if ($id > 0) {
|
if ($id > 0) {
|
||||||
|
|
||||||
|
// UPDATE existing stock_in record
|
||||||
$sql = "UPDATE `$table` SET
|
$sql = "UPDATE `$table` SET
|
||||||
`product_sku` = :product_sku,
|
`product_sku` = :product_sku,
|
||||||
`in` = :quantity,
|
`in` = :quantity,
|
||||||
`zone` = :zone,
|
`zone` = :zone,
|
||||||
`aisle` = :aisle,
|
`aisle` = :aisle,
|
||||||
`rack` = :rack,
|
`rack` = :rack,
|
||||||
`contact_id` = :contact_id,
|
`contact_id` = :contact_id,
|
||||||
`description` = :description,
|
`description` = :description,
|
||||||
`log` = :log
|
`log` = :log
|
||||||
WHERE id = :id AND company_id = :company_id";
|
WHERE id = :id AND company_id = :company_id";
|
||||||
|
|
||||||
$sth = $pdo->prepare($sql);
|
$sth = $pdo->prepare($sql);
|
||||||
$sth->execute([
|
$sth->execute([
|
||||||
":id" => $id,
|
":id" => $id,
|
||||||
":company_id" => $company_id,
|
":company_id" => $company_id,
|
||||||
":product_sku" => $data["product_sku"],
|
":product_sku" => $data["product_sku"],
|
||||||
":quantity" => $data["quantity"],
|
":quantity" => $data["quantity"],
|
||||||
":zone" => $data["zone"],
|
":zone" => $data["zone"],
|
||||||
":aisle" => $data["aisle"],
|
":aisle" => $data["aisle"],
|
||||||
":rack" => $data["rack"],
|
":rack" => $data["rack"],
|
||||||
":contact_id" => (int)$data["contact_id"],
|
":contact_id" => (int)$data["contact_id"],
|
||||||
":description" => $data["description"],
|
":description" => $data["description"],
|
||||||
":log" => json_encode($table_log),
|
":log" => json_encode($table_log),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// On UPDATE, td_stock_id stays the same (this record's ID)
|
||||||
|
$td_stock_id = $id;
|
||||||
|
|
||||||
|
// Only touch md_rack if the physical location actually changed.
|
||||||
|
// Skipping when unchanged avoids redundant log entries and release/occupy cycles.
|
||||||
|
$rack_changed = (
|
||||||
|
($row['zone'] ?? '') != $data['zone'] ||
|
||||||
|
($row['aisle'] ?? '') != $data['aisle'] ||
|
||||||
|
($row['rack'] ?? '') != $data['rack']
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($rack_changed) {
|
||||||
|
// Free up the old rack
|
||||||
|
$whMgmt->releaseRack(
|
||||||
|
$warehouse_id,
|
||||||
|
$row['zone'], $row['aisle'], $row['rack']
|
||||||
|
);
|
||||||
|
// Claim the new rack, linked to the same stock record
|
||||||
|
$whMgmt->occupyRack(
|
||||||
|
$warehouse_id,
|
||||||
|
$data['zone'], $data['aisle'], $data['rack'],
|
||||||
|
$data['product_sku'],
|
||||||
|
$td_stock_id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
// INSERT new stock_in record
|
||||||
$sql = "INSERT INTO `$table`
|
$sql = "INSERT INTO `$table`
|
||||||
(uuid, company_id, `date`, product_sku, `in`, zone, aisle, rack, contact_id, `description`, `log`, `type`)
|
(uuid, company_id, `date`, product_sku, `in`, zone, aisle, rack, contact_id, `description`, `log`, `type`)
|
||||||
VALUES
|
VALUES
|
||||||
@@ -55,22 +84,40 @@
|
|||||||
|
|
||||||
$sth = $pdo->prepare($sql);
|
$sth = $pdo->prepare($sql);
|
||||||
$sth->execute([
|
$sth->execute([
|
||||||
":uuid" => $uuid,
|
":uuid" => $uuid,
|
||||||
":company_id" => $company_id,
|
":company_id" => $company_id,
|
||||||
":date" => date('Y-m-d H:i:s'),
|
":date" => date('Y-m-d H:i:s'),
|
||||||
":product_sku" => $data["product_sku"],
|
":product_sku" => $data["product_sku"],
|
||||||
":quantity" => $data["quantity"],
|
":quantity" => $data["quantity"],
|
||||||
":zone" => $data["zone"],
|
":zone" => $data["zone"],
|
||||||
":aisle" => $data["aisle"],
|
":aisle" => $data["aisle"],
|
||||||
":rack" => $data["rack"],
|
":rack" => $data["rack"],
|
||||||
":contact_id" => (int)$data["contact_id"],
|
":contact_id" => (int)$data["contact_id"],
|
||||||
":description" => $data["description"],
|
":description" => $data["description"],
|
||||||
":log" => json_encode($table_log),
|
":log" => json_encode($table_log),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// 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
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$whMgmt->occupyRack($warehouse_id,$data["zone"],$data["aisle"],$data["rack"],$data["product_sku"]);
|
// Balance adjustment handles both INSERT (delta = full qty)
|
||||||
$whMgmt->adjustBalance('in', $warehouse_id, $data["product_sku"], $row['in'] ?? 0, $data["quantity"]);
|
// and UPDATE (delta = new - old) via the same formula.
|
||||||
|
$whMgmt->adjustBalance(
|
||||||
|
'in',
|
||||||
|
$warehouse_id,
|
||||||
|
$data["product_sku"],
|
||||||
|
$row['in'] ?? 0,
|
||||||
|
$data["quantity"]
|
||||||
|
);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -79,10 +126,12 @@
|
|||||||
} catch (PDOException $e) {
|
} catch (PDOException $e) {
|
||||||
$answer["success"] = 0;
|
$answer["success"] = 0;
|
||||||
$answer["message"] = "Database error, please try again";
|
$answer["message"] = "Database error, please try again";
|
||||||
|
http_response_code(500);
|
||||||
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
$answer["success"] = 0;
|
$answer["success"] = 0;
|
||||||
$answer["message"] = $e->getMessage();
|
$answer["message"] = $e->getMessage();
|
||||||
|
http_response_code(400);
|
||||||
}
|
}
|
||||||
|
|
||||||
exit(json_encode($answer));
|
exit(json_encode($answer));
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
session_start();
|
session_start();
|
||||||
|
|
||||||
require '../../../assets/utils/db_auth.php';
|
require '../../../assets/utils/db_auth.php';
|
||||||
require '../../../assets/utils/classes/WarehouseManager.php';
|
require '../../../assets/utils/classes/WarehouseManager.php';
|
||||||
|
|
||||||
@@ -16,61 +15,133 @@
|
|||||||
$row = $warehouse['row'] ?? [];
|
$row = $warehouse['row'] ?? [];
|
||||||
$table = $warehouse['table'];
|
$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 = json_decode($row['log'] ?? '[]', true);
|
||||||
$table_log[] = $logging;
|
$table_log[] = $logging;
|
||||||
|
|
||||||
|
// 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 check — 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']}."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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'];
|
||||||
|
|
||||||
if ($id > 0) {
|
if ($id > 0) {
|
||||||
|
|
||||||
|
// UPDATE existing stock_out record
|
||||||
$sql = "UPDATE `$table` SET
|
$sql = "UPDATE `$table` SET
|
||||||
`product_sku` = :product_sku,
|
`product_sku` = :product_sku,
|
||||||
`out` = :quantity,
|
`out` = :quantity,
|
||||||
`zone` = :zone,
|
`zone` = :zone,
|
||||||
`aisle` = :aisle,
|
`aisle` = :aisle,
|
||||||
`rack` = :rack,
|
`rack` = :rack,
|
||||||
`contact_id` = :contact_id,
|
`contact_id` = :contact_id,
|
||||||
`description` = :description,
|
`description` = :description,
|
||||||
`log` = :log
|
`log` = :log
|
||||||
WHERE id = :id AND company_id = :company_id";
|
WHERE id = :id AND company_id = :company_id";
|
||||||
|
|
||||||
$sth = $pdo->prepare($sql);
|
$sth = $pdo->prepare($sql);
|
||||||
$sth->execute([
|
$sth->execute([
|
||||||
":id" => $id,
|
":id" => $id,
|
||||||
":company_id" => $company_id,
|
":company_id" => $company_id,
|
||||||
":product_sku" => $data["product_sku"],
|
":product_sku" => $data["product_sku"],
|
||||||
":quantity" => $data["quantity"],
|
":quantity" => $quantity,
|
||||||
":zone" => $data["zone"],
|
":zone" => $data["zone"],
|
||||||
":aisle" => $data["aisle"],
|
":aisle" => $data["aisle"],
|
||||||
":rack" => $data["rack"],
|
":rack" => $data["rack"],
|
||||||
":contact_id" => (int)$data["contact_id"],
|
":contact_id" => (int)$data["contact_id"],
|
||||||
":description" => $data["description"],
|
":description" => $data["description"],
|
||||||
":log" => json_encode($table_log),
|
":log" => json_encode($table_log),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Only touch md_rack if the physical location actually changed.
|
||||||
|
$rack_changed = (
|
||||||
|
($row['zone'] ?? '') != $data['zone'] ||
|
||||||
|
($row['aisle'] ?? '') != $data['aisle'] ||
|
||||||
|
($row['rack'] ?? '') != $data['rack']
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($rack_changed) {
|
||||||
|
// Re-occupy the old rack with the batch it originally held
|
||||||
|
// (logically: the stock goes back to where it came from)
|
||||||
|
$whMgmt->occupyRack(
|
||||||
|
$warehouse_id,
|
||||||
|
$row['zone'], $row['aisle'], $row['rack'],
|
||||||
|
$row['product_sku'],
|
||||||
|
$row['ref_id'] ?? null
|
||||||
|
);
|
||||||
|
|
||||||
|
// Release the new rack (the real stock-out action)
|
||||||
|
$whMgmt->releaseRack(
|
||||||
|
$warehouse_id,
|
||||||
|
$data['zone'], $data['aisle'], $data['rack']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
// INSERT new stock_out record.
|
||||||
|
// ref_id links this stock_out row back to the stock_in batch it consumed.
|
||||||
$sql = "INSERT INTO `$table`
|
$sql = "INSERT INTO `$table`
|
||||||
(uuid, company_id, `date`, product_sku, `out`, zone, aisle, rack, contact_id, `description`, `log`, `type`)
|
(uuid, company_id, `date`, product_sku, `out`, zone, aisle, rack,
|
||||||
|
contact_id, `description`, `log`, `type`, ref_id)
|
||||||
VALUES
|
VALUES
|
||||||
(:uuid, :company_id, :date, :product_sku, :quantity, :zone, :aisle, :rack, :contact_id, :description, :log, 'out')";
|
(:uuid, :company_id, :date, :product_sku, :quantity, :zone, :aisle, :rack,
|
||||||
|
:contact_id, :description, :log, 'out', :ref_id)";
|
||||||
|
|
||||||
$sth = $pdo->prepare($sql);
|
$sth = $pdo->prepare($sql);
|
||||||
$sth->execute([
|
$sth->execute([
|
||||||
":uuid" => $uuid,
|
":uuid" => $uuid,
|
||||||
":company_id" => $company_id,
|
":company_id" => $company_id,
|
||||||
":date" => date('Y-m-d H:i:s'),
|
":date" => date('Y-m-d H:i:s'),
|
||||||
":product_sku" => $data["product_sku"],
|
":product_sku" => $data["product_sku"],
|
||||||
":quantity" => $data["quantity"],
|
":quantity" => $quantity,
|
||||||
":zone" => $data["zone"],
|
":zone" => $data["zone"],
|
||||||
":aisle" => $data["aisle"],
|
":aisle" => $data["aisle"],
|
||||||
":rack" => $data["rack"],
|
":rack" => $data["rack"],
|
||||||
":contact_id" => (int)$data["contact_id"],
|
":contact_id" => (int)$data["contact_id"],
|
||||||
":description" => $data["description"],
|
":description" => $data["description"],
|
||||||
":log" => json_encode($table_log),
|
":log" => json_encode($table_log),
|
||||||
|
":ref_id" => $ref_id,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Release the rack — stock is now gone from this location.
|
||||||
|
$whMgmt->releaseRack(
|
||||||
|
$warehouse_id,
|
||||||
|
$data["zone"], $data["aisle"], $data["rack"]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$whMgmt->releaseRack($warehouse_id, $data["zone"], $data["aisle"], $data["rack"],$data["product_sku"]);
|
// Balance: total_out increases by delta.
|
||||||
$whMgmt->adjustBalance('out', $warehouse_id, $data["product_sku"], $row['out'] ?? 0, $data["quantity"]);
|
// 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
|
||||||
|
);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -79,10 +150,12 @@
|
|||||||
} catch (PDOException $e) {
|
} catch (PDOException $e) {
|
||||||
$answer["success"] = 0;
|
$answer["success"] = 0;
|
||||||
$answer["message"] = "Database error, please try again";
|
$answer["message"] = "Database error, please try again";
|
||||||
|
http_response_code(500);
|
||||||
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
$answer["success"] = 0;
|
$answer["success"] = 0;
|
||||||
$answer["message"] = $e->getMessage();
|
$answer["message"] = $e->getMessage();
|
||||||
|
http_response_code(400);
|
||||||
}
|
}
|
||||||
|
|
||||||
exit(json_encode($answer));
|
exit(json_encode($answer));
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
session_start();
|
session_start();
|
||||||
|
|
||||||
require '../../../assets/utils/db_auth.php';
|
require '../../../assets/utils/db_auth.php';
|
||||||
require '../../../assets/utils/classes/WarehouseManager.php';
|
require '../../../assets/utils/classes/WarehouseManager.php';
|
||||||
|
|
||||||
@@ -8,101 +7,203 @@
|
|||||||
|
|
||||||
dbTransaction($pdo2, function($pdo) use ($data, $company_id, $logging, $uuid) {
|
dbTransaction($pdo2, function($pdo) use ($data, $company_id, $logging, $uuid) {
|
||||||
|
|
||||||
$id = (int)$data['id'];
|
$id = (int)($data['id'] ?? 0);
|
||||||
$whMgmt = new WarehouseManager($pdo, $company_id);
|
$whMgmt = new WarehouseManager($pdo, $company_id);
|
||||||
|
|
||||||
for ($i = 0; $i < 2; $i++) {
|
// Metadata that's editable on BOTH create and edit
|
||||||
|
$contact_id = (int)($data['contact_id'] ?? 0);
|
||||||
|
$description = ($data['description'] ?? '');
|
||||||
|
|
||||||
$data['type'] = ($i == 0) ? "from" : "to";
|
// ══════════════════════════════════════════════════════════
|
||||||
$data['ref_type'] = ($i == 0) ? "to" : "from";
|
// EDIT FLOW
|
||||||
$data['col'] = ($i == 0) ? "out" : "in";
|
// Physical state (racks, balances, quantities) is frozen.
|
||||||
|
// Only metadata (contact, description) can change.
|
||||||
|
// ══════════════════════════════════════════════════════════
|
||||||
|
if ($id > 0) {
|
||||||
|
|
||||||
$warehouse_id = (int)$data['warehouse_' . $data["type"]];
|
// Find the "from" row that's being edited.
|
||||||
$ref_warehouse_id = (int)$data['warehouse_' . $data["ref_type"]];
|
// We look it up across all td_stock_<wh> via warehouse_from
|
||||||
$data["warehouse"] = $data['warehouse_' . $data["type"]];
|
// since $id alone isn't enough (it's per-table).
|
||||||
$data["zone"] = $data['zone_' . $data["type"]];
|
$from_warehouse = (int)($data['warehouse_from'] ?? 0);
|
||||||
$data["aisle"] = $data['aisle_' . $data["type"]];
|
$to_warehouse = (int)($data['warehouse_to'] ?? 0);
|
||||||
$data["rack"] = $data['rack_' . $data["type"]];
|
|
||||||
|
|
||||||
$warehouse = $whMgmt->getStockContext($warehouse_id, $id);
|
$from_ctx = $whMgmt->getStockContext($from_warehouse, $id);
|
||||||
$row = $warehouse['row'] ?? [];
|
$from_table = $from_ctx['table'];
|
||||||
$table = $warehouse['table'];
|
$from_row = $from_ctx['row'];
|
||||||
|
|
||||||
// get uuid for update
|
if (!$from_row) {
|
||||||
if ($id > 0 && $data['type'] == "from") {
|
throw new Exception("Transfer record not found.");
|
||||||
$sql = "SELECT uuid FROM `$table`
|
|
||||||
WHERE company_id = :company_id AND id = :id";
|
|
||||||
$sth = $pdo->prepare($sql);
|
|
||||||
$sth->execute([
|
|
||||||
":company_id" => $company_id,
|
|
||||||
":id" => $id
|
|
||||||
]);
|
|
||||||
$uuid = $sth->fetchColumn();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$table_log = json_decode($row['log'] ?? '[]', true);
|
// Find the paired "to" row via ref_id stored on the "from" row
|
||||||
$table_log[] = $logging;
|
$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 ($id > 0) {
|
if (!$to_row) {
|
||||||
|
throw new Exception("Paired destination record missing — data integrity issue.");
|
||||||
$sql = "UPDATE `$table` SET
|
|
||||||
`product_sku` = :product_sku,
|
|
||||||
`{$data['col']}` = :quantity,
|
|
||||||
`zone` = :zone,
|
|
||||||
`aisle` = :aisle,
|
|
||||||
`rack` = :rack,
|
|
||||||
`contact_id` = :contact_id,
|
|
||||||
`description` = :description,
|
|
||||||
`log` = :log
|
|
||||||
WHERE uuid = :uuid AND company_id = :company_id";
|
|
||||||
|
|
||||||
$sth = $pdo->prepare($sql);
|
|
||||||
$sth->execute([
|
|
||||||
":uuid" => $uuid,
|
|
||||||
":company_id" => $company_id,
|
|
||||||
":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),
|
|
||||||
]);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
$sql = "INSERT INTO `$table`
|
|
||||||
(uuid, company_id, `date`, product_sku, `{$data['col']}`, ref_warehouse, zone, aisle, rack, contact_id, `description`, `log`, `type`)
|
|
||||||
VALUES
|
|
||||||
(:uuid, :company_id, :date, :product_sku, :quantity, :ref_warehouse, :zone, :aisle, :rack, :contact_id, :description, :log, 'transfer')";
|
|
||||||
|
|
||||||
$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"],
|
|
||||||
":ref_warehouse" => $ref_warehouse_id,
|
|
||||||
":zone" => $data["zone"],
|
|
||||||
":aisle" => $data["aisle"],
|
|
||||||
":rack" => $data["rack"],
|
|
||||||
":contact_id" => (int)$data["contact_id"],
|
|
||||||
":description" => $data["description"],
|
|
||||||
":log" => json_encode($table_log),
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if($data['type'] == 'from'){
|
// Append this edit to both rows' logs
|
||||||
$whMgmt->releaseRack($warehouse_id, $data["zone"], $data["aisle"], $data["rack"]);
|
$from_log = json_decode($from_row['log'] ?? '[]', true) ?: [];
|
||||||
}elseif($data['type'] == 'to'){
|
$from_log[] = $logging;
|
||||||
$whMgmt->occupyRack($warehouse_id, $data["zone"], $data["aisle"], $data["rack"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$whMgmt->adjustBalance($data['col'], $warehouse_id, $data["product_sku"], $row[$data['col']] ?? 0, $data["quantity"]);
|
$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']}."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$quantity = (int) $source_stock['in'];
|
||||||
|
|
||||||
|
// 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`)
|
||||||
|
VALUES
|
||||||
|
(:uuid, :company_id, :date, :product_sku, :quantity,
|
||||||
|
:ref_warehouse, :zone, :aisle, :rack,
|
||||||
|
:contact_id, :description, :log, 'transfer')";
|
||||||
|
$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),
|
||||||
|
]);
|
||||||
|
$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`)
|
||||||
|
VALUES
|
||||||
|
(:uuid, :company_id, :date, :product_sku, :quantity,
|
||||||
|
:ref_warehouse, :ref_id, :zone, :aisle, :rack,
|
||||||
|
:contact_id, :description, :log, 'transfer')";
|
||||||
|
$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),
|
||||||
|
]);
|
||||||
|
$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);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$answer["success"] = 1;
|
$answer["success"] = 1;
|
||||||
@@ -110,10 +211,12 @@
|
|||||||
} catch (PDOException $e) {
|
} catch (PDOException $e) {
|
||||||
$answer["success"] = 0;
|
$answer["success"] = 0;
|
||||||
$answer["message"] = "Database error, please try again";
|
$answer["message"] = "Database error, please try again";
|
||||||
|
http_response_code(500);
|
||||||
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
$answer["success"] = 0;
|
$answer["success"] = 0;
|
||||||
$answer["message"] = $e->getMessage();
|
$answer["message"] = $e->getMessage();
|
||||||
|
http_response_code(400);
|
||||||
}
|
}
|
||||||
|
|
||||||
exit(json_encode($answer));
|
exit(json_encode($answer));
|
||||||
|
|||||||
@@ -1,43 +1,64 @@
|
|||||||
<?php
|
<?php
|
||||||
session_start();
|
session_start();
|
||||||
|
|
||||||
require '../../../assets/utils/db_auth.php';
|
require '../../../assets/utils/db_auth.php';
|
||||||
|
|
||||||
$id = (int)$data['id'];
|
// Get warehouse and zone from the form
|
||||||
// identity source of zone and aisle
|
$warehouse = isset($data['warehouse']) ? (int)$data['warehouse'] : 0;
|
||||||
if(empty($data["type"])){
|
$zone = isset($data['zone']) ? $data['zone'] : '';
|
||||||
$warehouse = (int)$data['warehouse'];
|
|
||||||
$zone = $data['zone'];
|
if (isset($data['warehouse_from']) && $data['type'] == "from") {
|
||||||
}else{
|
$warehouse = (int)$data['warehouse_from'];
|
||||||
$warehouse = (int)$data['warehouse_'.$data["type"]];
|
$zone = $data['zone_from'];
|
||||||
$zone = $data['zone_'.$data["type"]];
|
}
|
||||||
|
if (isset($data['warehouse_to']) && $data['type'] == "to") {
|
||||||
|
$warehouse = (int)$data['warehouse_to'];
|
||||||
|
$zone = $data['zone_to'];
|
||||||
}
|
}
|
||||||
|
|
||||||
$sql = "SELECT *
|
// Filter aisles by operation type and optional SKU:
|
||||||
FROM md_storage
|
// 'to' = destination → aisles with at least one empty rack
|
||||||
WHERE
|
// 'from' + sku → aisles currently holding that specific SKU
|
||||||
company_id = :company_id and
|
// 'from' (no sku) → aisles with any occupied rack
|
||||||
warehouse = :warehouse and
|
// other = all aisles in the zone (admin / read-only pages)
|
||||||
`zone` = :zone
|
$product_sku_filter = "";
|
||||||
";
|
$params = [
|
||||||
|
":company_id" => $company_id,
|
||||||
|
":warehouse" => $warehouse,
|
||||||
|
":zone" => $zone,
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($data["type"] == "to") {
|
||||||
|
$product_sku_filter = "AND product_sku IS NULL";
|
||||||
|
|
||||||
|
} elseif ($data["type"] == "from") {
|
||||||
|
if (!empty($data["product_sku"])) {
|
||||||
|
$product_sku_filter = "AND product_sku = :product_sku";
|
||||||
|
$params[":product_sku"] = $data["product_sku"];
|
||||||
|
} else {
|
||||||
|
$product_sku_filter = "AND product_sku IS NOT NULL";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!empty($data["id"])){
|
||||||
|
$product_sku_filter = "";
|
||||||
|
unset($params[":product_sku"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = "SELECT DISTINCT aisle
|
||||||
|
FROM md_rack
|
||||||
|
WHERE
|
||||||
|
company_id = :company_id AND
|
||||||
|
warehouse = :warehouse AND
|
||||||
|
`zone` = :zone
|
||||||
|
$product_sku_filter
|
||||||
|
ORDER BY CAST(aisle AS UNSIGNED), aisle";
|
||||||
|
|
||||||
$sth = $pdo2->prepare($sql);
|
$sth = $pdo2->prepare($sql);
|
||||||
$sth->execute([
|
$sth->execute($params);
|
||||||
":company_id" => $company_id,
|
|
||||||
":warehouse" => $warehouse,
|
|
||||||
":zone" => $zone
|
|
||||||
]);
|
|
||||||
db_check($sth, $answer);
|
db_check($sth, $answer);
|
||||||
|
|
||||||
$pre = $sth->fetchAll(PDO::FETCH_ASSOC);
|
// Return flat array (matches original response shape for the frontend)
|
||||||
|
$answer["output"] = array_column($sth->fetchAll(PDO::FETCH_ASSOC), 'aisle');
|
||||||
$output = [];
|
|
||||||
foreach ($pre as $key => $value) {
|
|
||||||
$output = array_merge($output, range($value["aisle_from"], $value["aisle_to"]));
|
|
||||||
}
|
|
||||||
|
|
||||||
$answer["output"] = $output;
|
|
||||||
$answer["success"] = 1;
|
$answer["success"] = 1;
|
||||||
exit(json_encode($answer));
|
exit(json_encode($answer));
|
||||||
|
|
||||||
?>
|
?>
|
||||||
@@ -1,57 +1,69 @@
|
|||||||
<?php
|
<?php
|
||||||
session_start();
|
session_start();
|
||||||
|
|
||||||
require '../../../assets/utils/db_auth.php';
|
require '../../../assets/utils/db_auth.php';
|
||||||
|
|
||||||
// identity source of zone and aisle
|
// Get warehouse, zone, and aisle from the form
|
||||||
if(empty($data["type"])){
|
$warehouse = (int)$data['warehouse'];
|
||||||
$warehouse = (int)$data['warehouse'];
|
$zone = $data['zone'];
|
||||||
$zone = $data['zone'];
|
$aisle = $data['aisle'];
|
||||||
$aisle = $data["aisle"];
|
|
||||||
}else{
|
if (isset($data['warehouse_from']) && $data['type'] == "from") {
|
||||||
$warehouse = (int)$data['warehouse_'.$data["type"]];
|
$warehouse = (int)$data['warehouse_from'];
|
||||||
$zone = $data['zone_'.$data["type"]];
|
$zone = $data['zone_from'];
|
||||||
$aisle = $data["aisle_".$data["type"]];
|
$aisle = $data['aisle_from'];
|
||||||
|
}
|
||||||
|
if (isset($data['warehouse_to']) && $data['type'] == "to") {
|
||||||
|
$warehouse = (int)$data['warehouse_to'];
|
||||||
|
$zone = $data['zone_to'];
|
||||||
|
$aisle = $data['aisle_to'];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detect if numeric and zero-pad both input and query columns
|
// Filter racks by operation type and optional SKU:
|
||||||
if (is_numeric($aisle)) {
|
// 'to' = destination → empty racks only (product_sku IS NULL)
|
||||||
$aisle = str_pad($aisle, 5, '0', STR_PAD_LEFT);
|
// 'from' + sku → racks currently holding that specific SKU
|
||||||
$condition = "LPAD(aisle_from, 5, '0') <= :aisle_from AND LPAD(aisle_to, 5, '0') >= :aisle_to";
|
// 'from' (no sku) → any occupied rack (product_sku IS NOT NULL)
|
||||||
} else {
|
// other = all racks in the aisle (admin / read-only pages)
|
||||||
$condition = ":aisle_from >= aisle_from AND :aisle_to <= aisle_to";
|
$product_sku_filter = "";
|
||||||
|
$params = [
|
||||||
|
":company_id" => $company_id,
|
||||||
|
":warehouse" => $warehouse,
|
||||||
|
":zone" => $zone,
|
||||||
|
":aisle" => $aisle,
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($data["type"] == "to") {
|
||||||
|
$product_sku_filter = "AND product_sku IS NULL";
|
||||||
|
|
||||||
|
} elseif ($data["type"] == "from") {
|
||||||
|
if (!empty($data["product_sku"])) {
|
||||||
|
$product_sku_filter = "AND product_sku = :product_sku";
|
||||||
|
$params[":product_sku"] = $data["product_sku"];
|
||||||
|
} else {
|
||||||
|
$product_sku_filter = "AND product_sku IS NOT NULL";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$id = (int)$data['id'];
|
if(!empty($data["id"])){
|
||||||
|
$product_sku_filter = "";
|
||||||
|
unset($params[":product_sku"]);
|
||||||
|
}
|
||||||
|
|
||||||
$sql = "SELECT *
|
$sql = "SELECT DISTINCT rack
|
||||||
FROM md_storage
|
FROM md_rack
|
||||||
WHERE
|
WHERE
|
||||||
company_id = :company_id and
|
company_id = :company_id AND
|
||||||
warehouse = :warehouse and
|
warehouse = :warehouse AND
|
||||||
`zone` = :zone and
|
`zone` = :zone AND
|
||||||
$condition
|
aisle = :aisle
|
||||||
";
|
$product_sku_filter
|
||||||
|
ORDER BY CAST(rack AS UNSIGNED), rack";
|
||||||
|
|
||||||
$sth = $pdo2->prepare($sql);
|
$sth = $pdo2->prepare($sql);
|
||||||
$sth->execute([
|
$sth->execute($params);
|
||||||
":company_id" => $company_id,
|
|
||||||
":warehouse" => $warehouse,
|
|
||||||
":zone" => $zone,
|
|
||||||
":aisle_from" => $aisle,
|
|
||||||
":aisle_to" => $aisle
|
|
||||||
]);
|
|
||||||
db_check($sth, $answer);
|
db_check($sth, $answer);
|
||||||
|
|
||||||
$pre = $sth->fetchAll(PDO::FETCH_ASSOC);
|
// Return flat array (matches original response shape for the frontend)
|
||||||
|
$answer["output"] = array_column($sth->fetchAll(PDO::FETCH_ASSOC), 'rack');
|
||||||
$output = [];
|
|
||||||
foreach ($pre as $key => $value) {
|
|
||||||
$output = array_merge($output, range($value["rack_from"], $value["rack_to"]));
|
|
||||||
}
|
|
||||||
|
|
||||||
$answer["output"] = $output;
|
|
||||||
$answer["success"] = 1;
|
$answer["success"] = 1;
|
||||||
exit(json_encode($answer));
|
exit(json_encode($answer));
|
||||||
|
|
||||||
?>
|
?>
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
a.company_id = b.company_id and
|
a.company_id = b.company_id and
|
||||||
a.contact_id = b.id
|
a.contact_id = b.id
|
||||||
LEFT JOIN md_product c ON
|
LEFT JOIN md_product c ON
|
||||||
a.company_id = b.company_id and
|
a.company_id = c.company_id and
|
||||||
a.product_sku = c.sku
|
a.product_sku = c.sku
|
||||||
WHERE
|
WHERE
|
||||||
a.company_id = :company_id and
|
a.company_id = :company_id and
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
require '../../../assets/utils/db_auth.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;
|
||||||
|
exit(json_encode($answer));
|
||||||
|
?>
|
||||||
@@ -1,32 +1,59 @@
|
|||||||
<?php
|
<?php
|
||||||
session_start();
|
session_start();
|
||||||
|
|
||||||
require '../../../assets/utils/db_auth.php';
|
require '../../../assets/utils/db_auth.php';
|
||||||
|
|
||||||
$id = (int)$data['id'];
|
// Get warehouse from the form
|
||||||
|
$warehouse = (int)$data['warehouse'];
|
||||||
|
|
||||||
// identity source of zone and aisle
|
if(isset($data['warehouse_from'])&&$data['type']=="from"){
|
||||||
if(empty($data["type"])){
|
$warehouse = (int)$data['warehouse_from'];
|
||||||
$warehouse = (int)$data['warehouse'];
|
}
|
||||||
}else{
|
if(isset($data['warehouse_to'])&&$data['type']=="to"){
|
||||||
$warehouse = (int)$data['warehouse_'.$data["type"]];
|
$warehouse = (int)$data['warehouse_to'];
|
||||||
}
|
}
|
||||||
|
|
||||||
$sql = "SELECT *
|
// Filter zones by operation type and optional SKU:
|
||||||
FROM md_storage
|
// 'to' = destination → zones with at least one empty rack
|
||||||
WHERE
|
// 'from' + sku → zones currently holding that specific SKU
|
||||||
company_id = {$company_id} and
|
// 'from' (no sku) → zones with any occupied rack
|
||||||
warehouse = {$warehouse}";
|
// other = all zones in the warehouse (admin / read-only pages)
|
||||||
|
$product_sku_filter = "";
|
||||||
|
$params = [
|
||||||
|
":company_id" => $company_id,
|
||||||
|
":warehouse" => $warehouse,
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($data["type"] == "to") {
|
||||||
|
$product_sku_filter = "AND product_sku IS NULL";
|
||||||
|
|
||||||
|
} elseif ($data["type"] == "from") {
|
||||||
|
if (!empty($data["product_sku"])) {
|
||||||
|
$product_sku_filter = "AND product_sku = :product_sku";
|
||||||
|
$params[":product_sku"] = $data["product_sku"];
|
||||||
|
} else {
|
||||||
|
$product_sku_filter = "AND product_sku IS NOT NULL";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!empty($data["id"])){
|
||||||
|
$product_sku_filter = "";
|
||||||
|
unset($params[":product_sku"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = "SELECT DISTINCT zone
|
||||||
|
FROM md_rack
|
||||||
|
WHERE
|
||||||
|
company_id = :company_id AND
|
||||||
|
warehouse = :warehouse
|
||||||
|
$product_sku_filter
|
||||||
|
ORDER BY CAST(zone AS UNSIGNED), zone";
|
||||||
|
|
||||||
$sth = $pdo2->prepare($sql);
|
$sth = $pdo2->prepare($sql);
|
||||||
$sth->execute([
|
$sth->execute($params);
|
||||||
":company_id" => $company_id,
|
|
||||||
":warehouse" => $warehouse
|
|
||||||
]);
|
|
||||||
db_check($sth, $answer);
|
db_check($sth, $answer);
|
||||||
|
|
||||||
$answer["output"] = $sth->fetchAll(PDO::FETCH_ASSOC);
|
// Frontend expects objects with a `zone` property — keep that shape.
|
||||||
|
$answer["output"] = $sth->fetchAll(PDO::FETCH_ASSOC);
|
||||||
$answer["success"] = 1;
|
$answer["success"] = 1;
|
||||||
exit(json_encode($answer));
|
exit(json_encode($answer));
|
||||||
|
|
||||||
?>
|
?>
|
||||||
+24
-12
@@ -125,12 +125,15 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
function retrieve_warehouse() {
|
function retrieve_warehouse(type) {
|
||||||
|
|
||||||
return ajax_request({
|
return ajax_request({
|
||||||
url: "<?php echo $server_url?>inventory/api/engine/warehouse.php",
|
url: "<?php echo $server_url?>ics/api/engine/retrieve_warehouse.php",
|
||||||
autoPrepare: true,
|
autoPrepare: true,
|
||||||
checkRequired: 0,
|
checkRequired: 0,
|
||||||
|
data: {
|
||||||
|
"type": type
|
||||||
|
},
|
||||||
action: 'read',
|
action: 'read',
|
||||||
onSuccess: function(res) {
|
onSuccess: function(res) {
|
||||||
var option = `<option value="">Please select warehouse</option>`;
|
var option = `<option value="">Please select warehouse</option>`;
|
||||||
@@ -145,13 +148,16 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
function retrieve_zone(callback) {
|
function retrieve_zone(type, callback) {
|
||||||
|
|
||||||
return ajax_request({
|
return ajax_request({
|
||||||
url: "<?php echo $server_url?>ics/api/engine/retrieve_zone.php",
|
url: "<?php echo $server_url?>ics/api/engine/retrieve_zone.php",
|
||||||
autoPrepare: true,
|
autoPrepare: true,
|
||||||
checkRequired: 0,
|
checkRequired: 0,
|
||||||
noLoading: true,
|
noLoading: true,
|
||||||
|
data : {
|
||||||
|
"type": type
|
||||||
|
},
|
||||||
debugMode: 0,
|
debugMode: 0,
|
||||||
action: 'read',
|
action: 'read',
|
||||||
onSuccess: function(res) {
|
onSuccess: function(res) {
|
||||||
@@ -169,13 +175,16 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
function retrieve_aisle(callback) {
|
function retrieve_aisle(type, callback) {
|
||||||
|
|
||||||
return ajax_request({
|
return ajax_request({
|
||||||
url: "<?php echo $server_url?>ics/api/engine/retrieve_aisle.php",
|
url: "<?php echo $server_url?>ics/api/engine/retrieve_aisle.php",
|
||||||
autoPrepare: true,
|
autoPrepare: true,
|
||||||
checkRequired: 0,
|
checkRequired: 0,
|
||||||
noLoading: true,
|
noLoading: true,
|
||||||
|
data: {
|
||||||
|
"type": type
|
||||||
|
},
|
||||||
action: 'read',
|
action: 'read',
|
||||||
onSuccess: function(res) {
|
onSuccess: function(res) {
|
||||||
var option = `<option value="">Please select aisle</option>`;
|
var option = `<option value="">Please select aisle</option>`;
|
||||||
@@ -192,13 +201,16 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
function retrieve_rack(callback) {
|
function retrieve_rack(type, callback) {
|
||||||
|
|
||||||
return ajax_request({
|
return ajax_request({
|
||||||
url: "<?php echo $server_url?>ics/api/engine/retrieve_rack.php",
|
url: "<?php echo $server_url?>ics/api/engine/retrieve_rack.php",
|
||||||
autoPrepare: true,
|
autoPrepare: true,
|
||||||
checkRequired: 0,
|
checkRequired: 0,
|
||||||
noLoading: true,
|
noLoading: true,
|
||||||
|
data: {
|
||||||
|
"type": type
|
||||||
|
},
|
||||||
action: 'read',
|
action: 'read',
|
||||||
onSuccess: function(res) {
|
onSuccess: function(res) {
|
||||||
var option = `<option value="">Please select rack</option>`;
|
var option = `<option value="">Please select rack</option>`;
|
||||||
@@ -236,15 +248,15 @@
|
|||||||
$(`#warehouse`).val('<?php echo $_GET["wh"];?>').attr('disabled', true); // set warehouse BEFORE retrieve_zone
|
$(`#warehouse`).val('<?php echo $_GET["wh"];?>').attr('disabled', true); // set warehouse BEFORE retrieve_zone
|
||||||
|
|
||||||
// 2. Retrieve zone (autoPrepare reads warehouse value)
|
// 2. Retrieve zone (autoPrepare reads warehouse value)
|
||||||
retrieve_zone(function() {
|
retrieve_zone('to', function() {
|
||||||
$(`select#zone`).val(data.zone).attr('disabled', true); // set zone BEFORE retrieve_aisle
|
$(`select#zone`).val(data.zone).attr('disabled', true); // set zone BEFORE retrieve_aisle
|
||||||
|
|
||||||
// 3. Retrieve aisle (autoPrepare reads zone value)
|
// 3. Retrieve aisle (autoPrepare reads zone value)
|
||||||
retrieve_aisle(function() {
|
retrieve_aisle('to', function() {
|
||||||
$(`#aisle`).val(data.aisle).attr('disabled', true); // set aisle BEFORE retrieve_rack
|
$(`#aisle`).val(data.aisle).attr('disabled', true); // set aisle BEFORE retrieve_rack
|
||||||
|
|
||||||
// 4. Retrieve rack (autoPrepare reads aisle value)
|
// 4. Retrieve rack (autoPrepare reads aisle value)
|
||||||
retrieve_rack(function() {
|
retrieve_rack('to', function() {
|
||||||
$(`#rack`).val(data.rack).attr('disabled', true);
|
$(`#rack`).val(data.rack).attr('disabled', true);
|
||||||
|
|
||||||
// retrieve for edit
|
// retrieve for edit
|
||||||
@@ -280,7 +292,7 @@
|
|||||||
|
|
||||||
$(async function() {
|
$(async function() {
|
||||||
try {
|
try {
|
||||||
await retrieve_warehouse();
|
await retrieve_warehouse('to');
|
||||||
await retrieve_for_edit();
|
await retrieve_for_edit();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
@@ -289,15 +301,15 @@
|
|||||||
|
|
||||||
<?php if(empty($_GET["id"])){ ?>
|
<?php if(empty($_GET["id"])){ ?>
|
||||||
$("select#warehouse").change(function() {
|
$("select#warehouse").change(function() {
|
||||||
retrieve_zone();
|
retrieve_zone('to');
|
||||||
});
|
});
|
||||||
|
|
||||||
$("select#zone").change(function() {
|
$("select#zone").change(function() {
|
||||||
retrieve_aisle();
|
retrieve_aisle('to');
|
||||||
});
|
});
|
||||||
|
|
||||||
$("select#aisle").change(function() {
|
$("select#aisle").change(function() {
|
||||||
retrieve_rack();
|
retrieve_rack('to');
|
||||||
});
|
});
|
||||||
|
|
||||||
<?php } ?>
|
<?php } ?>
|
||||||
|
|||||||
@@ -40,8 +40,7 @@
|
|||||||
|
|
||||||
<!-- SKU or Product Code -->
|
<!-- SKU or Product Code -->
|
||||||
<div class="mb-3 col-lg-6">
|
<div class="mb-3 col-lg-6">
|
||||||
<label for="quantity" class="form-label">Quantity</label>
|
|
||||||
<input type="number" class="form-control" id="quantity" placeholder="Enter quantity" required>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
@@ -125,12 +124,15 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
function retrieve_warehouse() {
|
function retrieve_warehouse(type) {
|
||||||
|
|
||||||
return ajax_request({
|
return ajax_request({
|
||||||
url: "<?php echo $server_url?>inventory/api/engine/warehouse.php",
|
url: "<?php echo $server_url?>ics/api/engine/retrieve_warehouse.php",
|
||||||
autoPrepare: true,
|
autoPrepare: true,
|
||||||
checkRequired: 0,
|
checkRequired: 0,
|
||||||
|
data: {
|
||||||
|
"type": type
|
||||||
|
},
|
||||||
action: 'read',
|
action: 'read',
|
||||||
onSuccess: function(res) {
|
onSuccess: function(res) {
|
||||||
var option = `<option value="">Please select warehouse</option>`;
|
var option = `<option value="">Please select warehouse</option>`;
|
||||||
@@ -145,13 +147,16 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
function retrieve_zone(callback) {
|
function retrieve_zone(type,callback) {
|
||||||
|
|
||||||
return ajax_request({
|
return ajax_request({
|
||||||
url: "<?php echo $server_url?>ics/api/engine/retrieve_zone.php",
|
url: "<?php echo $server_url?>ics/api/engine/retrieve_zone.php",
|
||||||
autoPrepare: true,
|
autoPrepare: true,
|
||||||
checkRequired: 0,
|
checkRequired: 0,
|
||||||
noLoading: true,
|
noLoading: true,
|
||||||
|
data : {
|
||||||
|
"type": type
|
||||||
|
},
|
||||||
debugMode: 0,
|
debugMode: 0,
|
||||||
action: 'read',
|
action: 'read',
|
||||||
onSuccess: function(res) {
|
onSuccess: function(res) {
|
||||||
@@ -169,13 +174,16 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
function retrieve_aisle(callback) {
|
function retrieve_aisle(type, callback) {
|
||||||
|
|
||||||
return ajax_request({
|
return ajax_request({
|
||||||
url: "<?php echo $server_url?>ics/api/engine/retrieve_aisle.php",
|
url: "<?php echo $server_url?>ics/api/engine/retrieve_aisle.php",
|
||||||
autoPrepare: true,
|
autoPrepare: true,
|
||||||
checkRequired: 0,
|
checkRequired: 0,
|
||||||
noLoading: true,
|
noLoading: true,
|
||||||
|
data: {
|
||||||
|
"type": type
|
||||||
|
},
|
||||||
action: 'read',
|
action: 'read',
|
||||||
onSuccess: function(res) {
|
onSuccess: function(res) {
|
||||||
var option = `<option value="">Please select aisle</option>`;
|
var option = `<option value="">Please select aisle</option>`;
|
||||||
@@ -192,13 +200,16 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
function retrieve_rack(callback) {
|
function retrieve_rack(type, callback) {
|
||||||
|
|
||||||
return ajax_request({
|
return ajax_request({
|
||||||
url: "<?php echo $server_url?>ics/api/engine/retrieve_rack.php",
|
url: "<?php echo $server_url?>ics/api/engine/retrieve_rack.php",
|
||||||
autoPrepare: true,
|
autoPrepare: true,
|
||||||
checkRequired: 0,
|
checkRequired: 0,
|
||||||
noLoading: true,
|
noLoading: true,
|
||||||
|
data: {
|
||||||
|
"type": type
|
||||||
|
},
|
||||||
action: 'read',
|
action: 'read',
|
||||||
onSuccess: function(res) {
|
onSuccess: function(res) {
|
||||||
var option = `<option value="">Please select rack</option>`;
|
var option = `<option value="">Please select rack</option>`;
|
||||||
@@ -235,15 +246,15 @@
|
|||||||
$(`#warehouse`).val(<?php echo $_GET["wh"];?>).attr('disabled', true); // set warehouse BEFORE retrieve_zone
|
$(`#warehouse`).val(<?php echo $_GET["wh"];?>).attr('disabled', true); // set warehouse BEFORE retrieve_zone
|
||||||
|
|
||||||
// 2. Retrieve zone (autoPrepare reads warehouse value)
|
// 2. Retrieve zone (autoPrepare reads warehouse value)
|
||||||
retrieve_zone(function() {
|
retrieve_zone('from', function() {
|
||||||
$(`select#zone`).val(data.zone).attr('disabled', true); // set zone BEFORE retrieve_aisle
|
$(`select#zone`).val(data.zone).attr('disabled', true); // set zone BEFORE retrieve_aisle
|
||||||
|
|
||||||
// 3. Retrieve aisle (autoPrepare reads zone value)
|
// 3. Retrieve aisle (autoPrepare reads zone value)
|
||||||
retrieve_aisle(function() {
|
retrieve_aisle('from', function() {
|
||||||
$(`#aisle`).val(data.aisle).attr('disabled', true); // set aisle BEFORE retrieve_rack
|
$(`#aisle`).val(data.aisle).attr('disabled', true); // set aisle BEFORE retrieve_rack
|
||||||
|
|
||||||
// 4. Retrieve rack (autoPrepare reads aisle value)
|
// 4. Retrieve rack (autoPrepare reads aisle value)
|
||||||
retrieve_rack(function() {
|
retrieve_rack('from', function() {
|
||||||
$(`#rack`).val(data.rack).attr('disabled', true);
|
$(`#rack`).val(data.rack).attr('disabled', true);
|
||||||
|
|
||||||
// retrieve for edit
|
// retrieve for edit
|
||||||
@@ -279,24 +290,28 @@
|
|||||||
|
|
||||||
$(async function() {
|
$(async function() {
|
||||||
try {
|
try {
|
||||||
await retrieve_warehouse();
|
await retrieve_warehouse('from');
|
||||||
await retrieve_for_edit();
|
await retrieve_for_edit();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$(`#product_sku`).on('blur', function() {
|
||||||
|
retrieve_warehouse('from');
|
||||||
|
});
|
||||||
|
|
||||||
<?php if(empty($_GET["id"])){ ?>
|
<?php if(empty($_GET["id"])){ ?>
|
||||||
$("select#warehouse").change(function() {
|
$("select#warehouse").change(function() {
|
||||||
retrieve_zone();
|
retrieve_zone('from');
|
||||||
});
|
});
|
||||||
|
|
||||||
$("select#zone").change(function() {
|
$("select#zone").change(function() {
|
||||||
retrieve_aisle();
|
retrieve_aisle('from');
|
||||||
});
|
});
|
||||||
|
|
||||||
$("select#aisle").change(function() {
|
$("select#aisle").change(function() {
|
||||||
retrieve_rack();
|
retrieve_rack('from');
|
||||||
});
|
});
|
||||||
|
|
||||||
<?php } ?>
|
<?php } ?>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
<div class="col-lg-8 col-12">
|
<div class="col-lg-8 col-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body p-5">
|
<div class="card-body p-5">
|
||||||
<h2 class="fs-5 mb-4">Add Stock</h2>
|
<h2 class="fs-5 mb-4">Move Stock</h2>
|
||||||
<form class="row gx-5">
|
<form class="row gx-5">
|
||||||
<!-- Product Name -->
|
<!-- Product Name -->
|
||||||
<div class="mb-3 col-lg-3">
|
<div class="mb-3 col-lg-3">
|
||||||
@@ -40,8 +40,7 @@
|
|||||||
|
|
||||||
<!-- SKU or Product Code -->
|
<!-- SKU or Product Code -->
|
||||||
<div class="mb-3 col-lg-6">
|
<div class="mb-3 col-lg-6">
|
||||||
<label for="quantity" class="form-label">Quantity</label>
|
|
||||||
<input type="number" class="form-control" id="quantity" placeholder="Enter quantity" required>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
@@ -49,7 +48,9 @@
|
|||||||
<!-- Warehouse -->
|
<!-- Warehouse -->
|
||||||
<div class="mb-3 col-lg-3">
|
<div class="mb-3 col-lg-3">
|
||||||
<label for="warehouse" class="form-label">From Warehouse</label>
|
<label for="warehouse" class="form-label">From Warehouse</label>
|
||||||
<select name="warehouse" id="warehouse_from" role="from" class="form-select" required></select>
|
<select name="warehouse" id="warehouse_from" role="from" class="form-select" required>
|
||||||
|
<option value="">Please select warehouse</option>
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3 col-lg-3">
|
<div class="mb-3 col-lg-3">
|
||||||
@@ -77,7 +78,9 @@
|
|||||||
<!-- Warehouse -->
|
<!-- Warehouse -->
|
||||||
<div class="mb-3 col-lg-3">
|
<div class="mb-3 col-lg-3">
|
||||||
<label for="warehouse" class="form-label">To Warehouse</label>
|
<label for="warehouse" class="form-label">To Warehouse</label>
|
||||||
<select name="warehouse" id="warehouse_to" role="to" class="form-select" required></select>
|
<select name="warehouse" id="warehouse_to" role="to" class="form-select" required>
|
||||||
|
<option value="">Please select warehouse</option>
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3 col-lg-3">
|
<div class="mb-3 col-lg-3">
|
||||||
@@ -156,19 +159,23 @@
|
|||||||
function retrieve_warehouse(type) {
|
function retrieve_warehouse(type) {
|
||||||
|
|
||||||
return ajax_request({
|
return ajax_request({
|
||||||
url: "<?php echo $server_url?>inventory/api/engine/warehouse.php",
|
url: "<?php echo $server_url?>ics/api/engine/retrieve_warehouse.php",
|
||||||
autoPrepare: true,
|
autoPrepare: true,
|
||||||
checkRequired: 0,
|
checkRequired: 0,
|
||||||
data: {
|
data: {
|
||||||
"type": type
|
"type": type
|
||||||
},
|
},
|
||||||
action: 'read',
|
action: 'read',
|
||||||
|
debugMode: 0,
|
||||||
onSuccess: function(res) {
|
onSuccess: function(res) {
|
||||||
var option = `<option value="">Please select warehouse</option>`;
|
var option = `<option value="">Please select warehouse</option>`;
|
||||||
$.each(res.output, function(key, item) {
|
$.each(res.output, function(key, item) {
|
||||||
option += `<option value='${item.id}'>${item.warehouse_name}</option>`;
|
option += `<option value='${item.id}'>${item.warehouse_name}</option>`;
|
||||||
})
|
})
|
||||||
$(`select[name='warehouse'][role='${type}']`).html(option);
|
$(`select[name='warehouse'][role='${type}']`).html(option);
|
||||||
|
if(!type) {
|
||||||
|
$(`select[name='warehouse']`).html(option);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -195,6 +202,10 @@
|
|||||||
})
|
})
|
||||||
$(`select[name='zone'][role='${type}']`).html(option);
|
$(`select[name='zone'][role='${type}']`).html(option);
|
||||||
|
|
||||||
|
if(!type) {
|
||||||
|
$(`select[name='zone']`).html(option);
|
||||||
|
}
|
||||||
|
|
||||||
if (callback) callback(); // ← add this
|
if (callback) callback(); // ← add this
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -214,6 +225,7 @@
|
|||||||
},
|
},
|
||||||
noLoading: true,
|
noLoading: true,
|
||||||
action: 'read',
|
action: 'read',
|
||||||
|
debugMode: 0,
|
||||||
onSuccess: function(res) {
|
onSuccess: function(res) {
|
||||||
var option = `<option value="">Please select aisle</option>`;
|
var option = `<option value="">Please select aisle</option>`;
|
||||||
$.each(res.output, function(key, item) {
|
$.each(res.output, function(key, item) {
|
||||||
@@ -221,6 +233,10 @@
|
|||||||
})
|
})
|
||||||
$(`select[name='aisle'][role='${type}']`).html(option);
|
$(`select[name='aisle'][role='${type}']`).html(option);
|
||||||
|
|
||||||
|
if(!type) {
|
||||||
|
$(`select[name='aisle']`).html(option);
|
||||||
|
}
|
||||||
|
|
||||||
if (callback) callback(); // ← add this
|
if (callback) callback(); // ← add this
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -247,6 +263,10 @@
|
|||||||
})
|
})
|
||||||
$(`select[name='rack'][role='${type}']`).html(option);
|
$(`select[name='rack'][role='${type}']`).html(option);
|
||||||
|
|
||||||
|
if(!type) {
|
||||||
|
$(`select[name='rack']`).html(option);
|
||||||
|
}
|
||||||
|
|
||||||
if (callback) callback(); // ← add this
|
if (callback) callback(); // ← add this
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -324,7 +344,7 @@
|
|||||||
$('#contact').val(data.contact_name)
|
$('#contact').val(data.contact_name)
|
||||||
.attr('secondary', data.contact_name)
|
.attr('secondary', data.contact_name)
|
||||||
.attr('data-id', data.contact_id);
|
.attr('data-id', data.contact_id);
|
||||||
|
console.log(data);
|
||||||
$('#product_name').val(data.product_name);
|
$('#product_name').val(data.product_name);
|
||||||
$('#product_sku').attr('secondary', data.product_sku);
|
$('#product_sku').attr('secondary', data.product_sku);
|
||||||
|
|
||||||
@@ -340,14 +360,25 @@
|
|||||||
|
|
||||||
$(async function() {
|
$(async function() {
|
||||||
try {
|
try {
|
||||||
await retrieve_warehouse("from");
|
await retrieve_warehouse('from');
|
||||||
await retrieve_warehouse("to");
|
await retrieve_warehouse('to');
|
||||||
await retrieve_for_edit();
|
await retrieve_for_edit();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$(`#product_sku`).on('blur', function() {
|
||||||
|
$(async function() {
|
||||||
|
try {
|
||||||
|
await retrieve_warehouse('from');
|
||||||
|
await retrieve_warehouse('to');
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
<?php if(empty($_GET["id"])){ ?>
|
<?php if(empty($_GET["id"])){ ?>
|
||||||
$("select[name='warehouse']").change(function() {
|
$("select[name='warehouse']").change(function() {
|
||||||
retrieve_zone($(this).attr('role'));
|
retrieve_zone($(this).attr('role'));
|
||||||
|
|||||||
@@ -64,10 +64,12 @@
|
|||||||
} catch (PDOException $e) {
|
} catch (PDOException $e) {
|
||||||
$answer["success"] = 0;
|
$answer["success"] = 0;
|
||||||
$answer["message"] = "Database error, please try again";
|
$answer["message"] = "Database error, please try again";
|
||||||
|
http_response_code(500);
|
||||||
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
$answer["success"] = 0;
|
$answer["success"] = 0;
|
||||||
$answer["message"] = $e->getMessage();
|
$answer["message"] = $e->getMessage();
|
||||||
|
http_response_code(400);
|
||||||
}
|
}
|
||||||
|
|
||||||
exit(json_encode($answer));
|
exit(json_encode($answer));
|
||||||
|
|||||||
@@ -112,11 +112,13 @@
|
|||||||
} catch (PDOException $e) {
|
} catch (PDOException $e) {
|
||||||
$answer["success"] = 0;
|
$answer["success"] = 0;
|
||||||
$answer["message"] = "Database error, please try again";
|
$answer["message"] = "Database error, please try again";
|
||||||
|
http_response_code(500);
|
||||||
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
$answer["success"] = 0;
|
$answer["success"] = 0;
|
||||||
$answer["message"] = $e->getMessage();
|
$answer["message"] = $e->getMessage();
|
||||||
$answer["error"] = $e->getMessage();
|
$answer["error"] = $e->getMessage();
|
||||||
|
http_response_code(400);
|
||||||
}
|
}
|
||||||
|
|
||||||
exit(json_encode($answer));
|
exit(json_encode($answer));
|
||||||
|
|||||||
@@ -86,10 +86,12 @@
|
|||||||
} catch (PDOException $e) {
|
} catch (PDOException $e) {
|
||||||
$answer["success"] = 0;
|
$answer["success"] = 0;
|
||||||
$answer["message"] = "Database error, please try again";
|
$answer["message"] = "Database error, please try again";
|
||||||
|
http_response_code(500);
|
||||||
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
$answer["success"] = 0;
|
$answer["success"] = 0;
|
||||||
$answer["message"] = $e->getMessage();
|
$answer["message"] = $e->getMessage();
|
||||||
|
http_response_code(400);
|
||||||
}
|
}
|
||||||
|
|
||||||
exit(json_encode($answer));
|
exit(json_encode($answer));
|
||||||
|
|||||||
@@ -67,10 +67,12 @@
|
|||||||
} catch (PDOException $e) {
|
} catch (PDOException $e) {
|
||||||
$answer["success"] = 0;
|
$answer["success"] = 0;
|
||||||
$answer["message"] = "Database error, please try again";
|
$answer["message"] = "Database error, please try again";
|
||||||
|
http_response_code(500);
|
||||||
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
$answer["success"] = 0;
|
$answer["success"] = 0;
|
||||||
$answer["message"] = $e->getMessage();
|
$answer["message"] = $e->getMessage();
|
||||||
|
http_response_code(400);
|
||||||
}
|
}
|
||||||
|
|
||||||
exit(json_encode($answer));
|
exit(json_encode($answer));
|
||||||
|
|||||||
@@ -1,54 +1,80 @@
|
|||||||
<?php
|
<?php
|
||||||
session_start();
|
session_start();
|
||||||
|
|
||||||
require '../../../assets/utils/db_auth.php';
|
require '../../../assets/utils/db_auth.php';
|
||||||
|
|
||||||
$id = (int)$data['id'];
|
try {
|
||||||
|
$result = dbTransaction($pdo2, function($pdo) use ($data, $company_id, $logging) {
|
||||||
|
|
||||||
// get data
|
$id = (int)($data['id'] ?? 0);
|
||||||
$sql = "SELECT *
|
|
||||||
FROM md_product_category
|
|
||||||
WHERE
|
|
||||||
company_id = :company_id and
|
|
||||||
id = :id ";
|
|
||||||
$sth = $pdo2->prepare($sql);
|
|
||||||
$sth->execute([
|
|
||||||
":company_id" => $company_id,
|
|
||||||
":id" => $id
|
|
||||||
]);
|
|
||||||
db_check($sth, $answer);
|
|
||||||
$json = $sth->fetch(PDO::FETCH_ASSOC);
|
|
||||||
$json = json_encode($json);
|
|
||||||
|
|
||||||
|
if ($id <= 0) {
|
||||||
|
throw new Exception("Invalid category ID.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Validate: check if category is in use by any product
|
||||||
|
$sql = "SELECT COUNT(*)
|
||||||
|
FROM md_product
|
||||||
|
WHERE company_id = :company_id
|
||||||
|
AND category = :id";
|
||||||
|
$sth = $pdo->prepare($sql);
|
||||||
|
$sth->execute([
|
||||||
|
":company_id" => $company_id,
|
||||||
|
":id" => $id
|
||||||
|
]);
|
||||||
|
|
||||||
// delete data
|
$product_count = (int) $sth->fetchColumn();
|
||||||
$sql = "DELETE FROM md_product_category
|
|
||||||
WHERE
|
|
||||||
company_id = :company_id and
|
|
||||||
id = :id ";
|
|
||||||
$sth = $pdo2->prepare($sql);
|
|
||||||
$sth->execute([
|
|
||||||
":company_id" => $id,
|
|
||||||
":id" => $id
|
|
||||||
]);
|
|
||||||
db_check($sth, $answer);
|
|
||||||
|
|
||||||
|
if ($product_count > 0) {
|
||||||
|
throw new Exception("Cannot remove category. It is currently used by {$product_count} product(s).");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Append to log history
|
||||||
|
$sql = "SELECT `log` FROM md_product_category
|
||||||
|
WHERE company_id = :company_id AND id = :id";
|
||||||
|
$sth = $pdo->prepare($sql);
|
||||||
|
$sth->execute([
|
||||||
|
":company_id" => $company_id,
|
||||||
|
":id" => $id
|
||||||
|
]);
|
||||||
|
|
||||||
// insert into archive
|
$table_log = json_decode($sth->fetchColumn() ?: '[]', true);
|
||||||
$sql = "INSERT INTO archive (`company_id`,`user_id`,`date`,`json`) VALUES (:company_id,:user_id,NOW(),:json)";
|
if (!is_array($table_log)) $table_log = [];
|
||||||
$sth = $pdo2->prepare($sql);
|
$table_log[] = $logging;
|
||||||
$sth->execute([
|
|
||||||
":company_id" => $company_id,
|
|
||||||
":user_id" => $user_id,
|
|
||||||
":json" => $json
|
|
||||||
]);
|
|
||||||
db_check($sth, $answer);
|
|
||||||
|
|
||||||
|
// 3. Soft-delete via sign flip
|
||||||
|
$sql = "UPDATE md_product_category
|
||||||
|
SET company_id = company_id * (-1),
|
||||||
|
`log` = :log
|
||||||
|
WHERE company_id = :company_id
|
||||||
|
AND id = :id";
|
||||||
|
$sth = $pdo->prepare($sql);
|
||||||
|
$sth->execute([
|
||||||
|
":company_id" => $company_id,
|
||||||
|
":id" => $id,
|
||||||
|
":log" => json_encode($table_log, JSON_UNESCAPED_UNICODE),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($sth->rowCount() === 0) {
|
||||||
|
throw new Exception("Category not found or already removed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return $id;
|
||||||
|
});
|
||||||
|
|
||||||
|
$answer["success"] = 1;
|
||||||
|
$answer["message"] = "Category removed successfully.";
|
||||||
|
$answer["id"] = $result;
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$answer["success"] = 0;
|
||||||
|
$answer["message"] = "Database error, please try again";
|
||||||
|
http_response_code(500);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$answer["success"] = 0;
|
||||||
|
$answer["message"] = $e->getMessage();
|
||||||
|
http_response_code(400);
|
||||||
|
}
|
||||||
|
|
||||||
$answer["success"] = 1;
|
|
||||||
exit(json_encode($answer));
|
exit(json_encode($answer));
|
||||||
|
|
||||||
?>
|
?>
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
$answer["total_location"] = $report->getTotalLocation();
|
$answer["total_location"] = $report->getTotalLocation();
|
||||||
$answer["total_capacity"] = $report->getTotalCapacity();
|
$answer["total_capacity"] = $report->getTotalCapacity();
|
||||||
$answer["space_used"] = $report->getSpaceUsed();
|
$answer["space_used"] = $report->getSpaceUsed();
|
||||||
$answer["total_product"] = $report->getTotalProduct();
|
$answer["total_product"] = $report->getTotalProductInStock();
|
||||||
$answer["success"] = 1;
|
$answer["success"] = 1;
|
||||||
|
|
||||||
exit(json_encode($answer));
|
exit(json_encode($answer));
|
||||||
|
|||||||
@@ -150,7 +150,7 @@
|
|||||||
<th>Category</th>
|
<th>Category</th>
|
||||||
<th>Slug</th>
|
<th>Slug</th>
|
||||||
<th>Description</th>
|
<th>Description</th>
|
||||||
<th>Products</th>
|
<th style="text-align: right;">Product Count</th>
|
||||||
<th>Status</th>
|
<th>Status</th>
|
||||||
<th>Actions</th>
|
<th>Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -190,11 +190,12 @@
|
|||||||
<table class="table table-hover mb-0 table-centered" id="product">
|
<table class="table table-hover mb-0 table-centered" id="product">
|
||||||
<thead class="table-secondary border-light">
|
<thead class="table-secondary border-light">
|
||||||
<tr>
|
<tr>
|
||||||
|
<th width="75px"></th>
|
||||||
<th>Product</th>
|
<th>Product</th>
|
||||||
<th>SKU</th>
|
<th>SKU</th>
|
||||||
<th>Current Stock</th>
|
<th style="text-align: right;">Current Stock</th>
|
||||||
<th>Min Stock</th>
|
<th style="text-align: right;">Min Stock</th>
|
||||||
<th>Reorder Point</th>
|
<th style="text-align: right;">Reorder Point</th>
|
||||||
<th>Status</th>
|
<th>Status</th>
|
||||||
<th>Action</th>
|
<th>Action</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -309,7 +310,7 @@
|
|||||||
<td class="py-3">${item['category']}</td>
|
<td class="py-3">${item['category']}</td>
|
||||||
<td class="py-3">${item['slug']}</td>
|
<td class="py-3">${item['slug']}</td>
|
||||||
<td class="py-3">${item['description']}</td>
|
<td class="py-3">${item['description']}</td>
|
||||||
<td class="py-3">${item['product_count']}</td>
|
<td class="py-3" style="text-align: right;">${item['product_count']}</td>
|
||||||
<td class="py-3">
|
<td class="py-3">
|
||||||
${(item['status']==1)?'<span class="badge bg-success">Active</span>':'<span class="badge bg-secondary">Inactive</span>'}
|
${(item['status']==1)?'<span class="badge bg-success">Active</span>':'<span class="badge bg-secondary">Inactive</span>'}
|
||||||
</td>
|
</td>
|
||||||
@@ -367,7 +368,10 @@
|
|||||||
|
|
||||||
var body = ``;
|
var body = ``;
|
||||||
$.each(page_data, function(key, item) {
|
$.each(page_data, function(key, item) {
|
||||||
body += `<tr>
|
body += `<tr height="50px">
|
||||||
|
<td class="py-3">
|
||||||
|
<img src="<?php echo $server_url?>uploads/product/${item['product_image']}" alt="${item['product_name']}" class="img-fluid" style="height: 25px;">
|
||||||
|
</td>
|
||||||
<td class="py-3">${item['product_name']}</td>
|
<td class="py-3">${item['product_name']}</td>
|
||||||
<td class="py-3">${item['sku']}</td>
|
<td class="py-3">${item['sku']}</td>
|
||||||
<td class="py-3" style='text-align: right;'>${format_number(item['product_balance'],4)}</td>
|
<td class="py-3" style='text-align: right;'>${format_number(item['product_balance'],4)}</td>
|
||||||
|
|||||||
Reference in New Issue
Block a user