generic API response + ICS: warehouse_balance md_rack update
This commit is contained in:
@@ -16,38 +16,67 @@
|
||||
$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
|
||||
`product_sku` = :product_sku,
|
||||
`in` = :quantity,
|
||||
`zone` = :zone,
|
||||
`aisle` = :aisle,
|
||||
`rack` = :rack,
|
||||
`contact_id` = :contact_id,
|
||||
`in` = :quantity,
|
||||
`zone` = :zone,
|
||||
`aisle` = :aisle,
|
||||
`rack` = :rack,
|
||||
`contact_id` = :contact_id,
|
||||
`description` = :description,
|
||||
`log` = :log
|
||||
`log` = :log
|
||||
WHERE id = :id AND company_id = :company_id";
|
||||
|
||||
$sth = $pdo->prepare($sql);
|
||||
$sth->execute([
|
||||
":id" => $id,
|
||||
":company_id" => $company_id,
|
||||
":id" => $id,
|
||||
":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"],
|
||||
":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),
|
||||
":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 {
|
||||
|
||||
// INSERT new stock_in record
|
||||
$sql = "INSERT INTO `$table`
|
||||
(uuid, company_id, `date`, product_sku, `in`, zone, aisle, rack, contact_id, `description`, `log`, `type`)
|
||||
VALUES
|
||||
@@ -55,22 +84,40 @@
|
||||
|
||||
$sth = $pdo->prepare($sql);
|
||||
$sth->execute([
|
||||
":uuid" => $uuid,
|
||||
":company_id" => $company_id,
|
||||
":date" => date('Y-m-d H:i:s'),
|
||||
":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"],
|
||||
":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),
|
||||
":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"]);
|
||||
$whMgmt->adjustBalance('in', $warehouse_id, $data["product_sku"], $row['in'] ?? 0, $data["quantity"]);
|
||||
// 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"]
|
||||
);
|
||||
|
||||
});
|
||||
|
||||
@@ -79,10 +126,12 @@
|
||||
} 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);
|
||||
}
|
||||
|
||||
exit(json_encode($answer));
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
require '../../../assets/utils/db_auth.php';
|
||||
require '../../../assets/utils/classes/WarehouseManager.php';
|
||||
|
||||
@@ -16,61 +15,133 @@
|
||||
$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;
|
||||
|
||||
// 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) {
|
||||
|
||||
// UPDATE existing stock_out record
|
||||
$sql = "UPDATE `$table` SET
|
||||
`product_sku` = :product_sku,
|
||||
`out` = :quantity,
|
||||
`zone` = :zone,
|
||||
`aisle` = :aisle,
|
||||
`rack` = :rack,
|
||||
`contact_id` = :contact_id,
|
||||
`out` = :quantity,
|
||||
`zone` = :zone,
|
||||
`aisle` = :aisle,
|
||||
`rack` = :rack,
|
||||
`contact_id` = :contact_id,
|
||||
`description` = :description,
|
||||
`log` = :log
|
||||
`log` = :log
|
||||
WHERE id = :id AND company_id = :company_id";
|
||||
|
||||
$sth = $pdo->prepare($sql);
|
||||
$sth->execute([
|
||||
":id" => $id,
|
||||
":company_id" => $company_id,
|
||||
":id" => $id,
|
||||
":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"],
|
||||
":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),
|
||||
":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 {
|
||||
|
||||
// INSERT new stock_out record.
|
||||
// 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`)
|
||||
(uuid, company_id, `date`, product_sku, `out`, zone, aisle, rack,
|
||||
contact_id, `description`, `log`, `type`, ref_id)
|
||||
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->execute([
|
||||
":uuid" => $uuid,
|
||||
":company_id" => $company_id,
|
||||
":date" => date('Y-m-d H:i:s'),
|
||||
":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"],
|
||||
":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),
|
||||
":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"]);
|
||||
$whMgmt->adjustBalance('out', $warehouse_id, $data["product_sku"], $row['out'] ?? 0, $data["quantity"]);
|
||||
// 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
|
||||
);
|
||||
|
||||
});
|
||||
|
||||
@@ -79,10 +150,12 @@
|
||||
} 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();
|
||||
$answer["message"] = $e->getMessage();
|
||||
http_response_code(400);
|
||||
}
|
||||
|
||||
exit(json_encode($answer));
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
require '../../../assets/utils/db_auth.php';
|
||||
require '../../../assets/utils/classes/WarehouseManager.php';
|
||||
|
||||
@@ -8,101 +7,203 @@
|
||||
|
||||
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);
|
||||
|
||||
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";
|
||||
$data['col'] = ($i == 0) ? "out" : "in";
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// EDIT FLOW
|
||||
// Physical state (racks, balances, quantities) is frozen.
|
||||
// Only metadata (contact, description) can change.
|
||||
// ══════════════════════════════════════════════════════════
|
||||
if ($id > 0) {
|
||||
|
||||
$warehouse_id = (int)$data['warehouse_' . $data["type"]];
|
||||
$ref_warehouse_id = (int)$data['warehouse_' . $data["ref_type"]];
|
||||
$data["warehouse"] = $data['warehouse_' . $data["type"]];
|
||||
$data["zone"] = $data['zone_' . $data["type"]];
|
||||
$data["aisle"] = $data['aisle_' . $data["type"]];
|
||||
$data["rack"] = $data['rack_' . $data["type"]];
|
||||
// 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);
|
||||
|
||||
$warehouse = $whMgmt->getStockContext($warehouse_id, $id);
|
||||
$row = $warehouse['row'] ?? [];
|
||||
$table = $warehouse['table'];
|
||||
$from_ctx = $whMgmt->getStockContext($from_warehouse, $id);
|
||||
$from_table = $from_ctx['table'];
|
||||
$from_row = $from_ctx['row'];
|
||||
|
||||
// get uuid for update
|
||||
if ($id > 0 && $data['type'] == "from") {
|
||||
$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();
|
||||
if (!$from_row) {
|
||||
throw new Exception("Transfer record not found.");
|
||||
}
|
||||
|
||||
$table_log = json_decode($row['log'] ?? '[]', true);
|
||||
$table_log[] = $logging;
|
||||
// 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 ($id > 0) {
|
||||
|
||||
$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'){
|
||||
$whMgmt->releaseRack($warehouse_id, $data["zone"], $data["aisle"], $data["rack"]);
|
||||
}elseif($data['type'] == 'to'){
|
||||
$whMgmt->occupyRack($warehouse_id, $data["zone"], $data["aisle"], $data["rack"]);
|
||||
if (!$to_row) {
|
||||
throw new Exception("Paired destination record missing — data integrity issue.");
|
||||
}
|
||||
|
||||
$whMgmt->adjustBalance($data['col'], $warehouse_id, $data["product_sku"], $row[$data['col']] ?? 0, $data["quantity"]);
|
||||
// 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']}."
|
||||
);
|
||||
}
|
||||
|
||||
$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;
|
||||
@@ -110,10 +211,12 @@
|
||||
} 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();
|
||||
$answer["message"] = $e->getMessage();
|
||||
http_response_code(400);
|
||||
}
|
||||
|
||||
exit(json_encode($answer));
|
||||
|
||||
@@ -1,43 +1,64 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
require '../../../assets/utils/db_auth.php';
|
||||
|
||||
$id = (int)$data['id'];
|
||||
// identity source of zone and aisle
|
||||
if(empty($data["type"])){
|
||||
$warehouse = (int)$data['warehouse'];
|
||||
$zone = $data['zone'];
|
||||
}else{
|
||||
$warehouse = (int)$data['warehouse_'.$data["type"]];
|
||||
$zone = $data['zone_'.$data["type"]];
|
||||
// Get warehouse and zone from the form
|
||||
$warehouse = isset($data['warehouse']) ? (int)$data['warehouse'] : 0;
|
||||
$zone = isset($data['zone']) ? $data['zone'] : '';
|
||||
|
||||
if (isset($data['warehouse_from']) && $data['type'] == "from") {
|
||||
$warehouse = (int)$data['warehouse_from'];
|
||||
$zone = $data['zone_from'];
|
||||
}
|
||||
if (isset($data['warehouse_to']) && $data['type'] == "to") {
|
||||
$warehouse = (int)$data['warehouse_to'];
|
||||
$zone = $data['zone_to'];
|
||||
}
|
||||
|
||||
$sql = "SELECT *
|
||||
FROM md_storage
|
||||
WHERE
|
||||
company_id = :company_id and
|
||||
warehouse = :warehouse and
|
||||
`zone` = :zone
|
||||
";
|
||||
// Filter aisles by operation type and optional SKU:
|
||||
// 'to' = destination → aisles with at least one empty rack
|
||||
// 'from' + sku → aisles currently holding that specific SKU
|
||||
// 'from' (no sku) → aisles with any occupied rack
|
||||
// other = all aisles in the zone (admin / read-only pages)
|
||||
$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->execute([
|
||||
":company_id" => $company_id,
|
||||
":warehouse" => $warehouse,
|
||||
":zone" => $zone
|
||||
]);
|
||||
$sth->execute($params);
|
||||
db_check($sth, $answer);
|
||||
|
||||
$pre = $sth->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$output = [];
|
||||
foreach ($pre as $key => $value) {
|
||||
$output = array_merge($output, range($value["aisle_from"], $value["aisle_to"]));
|
||||
}
|
||||
|
||||
$answer["output"] = $output;
|
||||
// Return flat array (matches original response shape for the frontend)
|
||||
$answer["output"] = array_column($sth->fetchAll(PDO::FETCH_ASSOC), 'aisle');
|
||||
$answer["success"] = 1;
|
||||
exit(json_encode($answer));
|
||||
|
||||
?>
|
||||
@@ -1,57 +1,69 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
require '../../../assets/utils/db_auth.php';
|
||||
|
||||
// identity source of zone and aisle
|
||||
if(empty($data["type"])){
|
||||
$warehouse = (int)$data['warehouse'];
|
||||
$zone = $data['zone'];
|
||||
$aisle = $data["aisle"];
|
||||
}else{
|
||||
$warehouse = (int)$data['warehouse_'.$data["type"]];
|
||||
$zone = $data['zone_'.$data["type"]];
|
||||
$aisle = $data["aisle_".$data["type"]];
|
||||
// Get warehouse, zone, and aisle from the form
|
||||
$warehouse = (int)$data['warehouse'];
|
||||
$zone = $data['zone'];
|
||||
$aisle = $data['aisle'];
|
||||
|
||||
if (isset($data['warehouse_from']) && $data['type'] == "from") {
|
||||
$warehouse = (int)$data['warehouse_from'];
|
||||
$zone = $data['zone_from'];
|
||||
$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
|
||||
if (is_numeric($aisle)) {
|
||||
$aisle = str_pad($aisle, 5, '0', STR_PAD_LEFT);
|
||||
$condition = "LPAD(aisle_from, 5, '0') <= :aisle_from AND LPAD(aisle_to, 5, '0') >= :aisle_to";
|
||||
} else {
|
||||
$condition = ":aisle_from >= aisle_from AND :aisle_to <= aisle_to";
|
||||
// Filter racks by operation type and optional SKU:
|
||||
// 'to' = destination → empty racks only (product_sku IS NULL)
|
||||
// 'from' + sku → racks currently holding that specific SKU
|
||||
// 'from' (no sku) → any occupied rack (product_sku IS NOT NULL)
|
||||
// other = all racks in the aisle (admin / read-only pages)
|
||||
$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 *
|
||||
FROM md_storage
|
||||
WHERE
|
||||
company_id = :company_id and
|
||||
warehouse = :warehouse and
|
||||
`zone` = :zone and
|
||||
$condition
|
||||
";
|
||||
$sql = "SELECT DISTINCT rack
|
||||
FROM md_rack
|
||||
WHERE
|
||||
company_id = :company_id AND
|
||||
warehouse = :warehouse AND
|
||||
`zone` = :zone AND
|
||||
aisle = :aisle
|
||||
$product_sku_filter
|
||||
ORDER BY CAST(rack AS UNSIGNED), rack";
|
||||
|
||||
$sth = $pdo2->prepare($sql);
|
||||
$sth->execute([
|
||||
":company_id" => $company_id,
|
||||
":warehouse" => $warehouse,
|
||||
":zone" => $zone,
|
||||
":aisle_from" => $aisle,
|
||||
":aisle_to" => $aisle
|
||||
]);
|
||||
$sth->execute($params);
|
||||
db_check($sth, $answer);
|
||||
|
||||
$pre = $sth->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$output = [];
|
||||
foreach ($pre as $key => $value) {
|
||||
$output = array_merge($output, range($value["rack_from"], $value["rack_to"]));
|
||||
}
|
||||
|
||||
$answer["output"] = $output;
|
||||
// Return flat array (matches original response shape for the frontend)
|
||||
$answer["output"] = array_column($sth->fetchAll(PDO::FETCH_ASSOC), 'rack');
|
||||
$answer["success"] = 1;
|
||||
exit(json_encode($answer));
|
||||
|
||||
?>
|
||||
@@ -35,7 +35,7 @@
|
||||
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.company_id = c.company_id and
|
||||
a.product_sku = c.sku
|
||||
WHERE
|
||||
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
|
||||
session_start();
|
||||
|
||||
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(empty($data["type"])){
|
||||
$warehouse = (int)$data['warehouse'];
|
||||
}else{
|
||||
$warehouse = (int)$data['warehouse_'.$data["type"]];
|
||||
if(isset($data['warehouse_from'])&&$data['type']=="from"){
|
||||
$warehouse = (int)$data['warehouse_from'];
|
||||
}
|
||||
if(isset($data['warehouse_to'])&&$data['type']=="to"){
|
||||
$warehouse = (int)$data['warehouse_to'];
|
||||
}
|
||||
|
||||
$sql = "SELECT *
|
||||
FROM md_storage
|
||||
WHERE
|
||||
company_id = {$company_id} and
|
||||
warehouse = {$warehouse}";
|
||||
// Filter zones by operation type and optional SKU:
|
||||
// 'to' = destination → zones with at least one empty rack
|
||||
// 'from' + sku → zones currently holding that specific SKU
|
||||
// 'from' (no sku) → zones with any occupied rack
|
||||
// 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->execute([
|
||||
":company_id" => $company_id,
|
||||
":warehouse" => $warehouse
|
||||
]);
|
||||
$sth->execute($params);
|
||||
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;
|
||||
exit(json_encode($answer));
|
||||
|
||||
?>
|
||||
Reference in New Issue
Block a user