getStockContext($warehouse_id, $id); $row = $warehouse['row'] ?? []; $table = $warehouse['table']; // Append this edit/create event to td_stock_.log (stock record history) $table_log = json_decode($row['log'] ?? '[]', true); $table_log[] = $logging; if ($id > 0) { // UPDATE existing stock_out record $sql = "UPDATE `$table` SET `contact_id` = :contact_id, `description` = :description, `log` = :log WHERE id = :id AND company_id = :company_id"; $sth = $pdo->prepare($sql); $sth->execute([ ":id" => $id, ":company_id" => $company_id, ":contact_id" => (int)$data["contact_id"], ":description" => $data["description"], ":log" => json_encode($table_log), ]); } else { // Look up the source stock record via the rack. // Under 1:1, the rack points to exactly one td_stock_ row. $source_stock = $whMgmt->getRackStock( $warehouse_id, $data["zone"], $data["aisle"], $data["rack"] ); if (!$source_stock) { throw new Exception( "Rack {$data['zone']}-{$data['aisle']}-{$data['rack']} is empty — nothing to take out." ); } // Defensive checks — UI cascade should already prevent mismatches if ($source_stock['product_sku'] !== $data["product_sku"]) { throw new Exception( "Rack holds {$source_stock['product_sku']}, not {$data['product_sku']}." ); } // If user selected a specific lot, verify it matches the rack's stock if (!empty($data["lot_number"]) && $source_stock['lot_number'] !== $data["lot_number"]) { throw new Exception( "Rack holds lot '{$source_stock['lot_number']}', not '{$data['lot_number']}'." ); } // If user selected a specific serial, verify it matches the rack's stock if (!empty($data["serial_number"]) && $source_stock['serial_number'] !== $data["serial_number"]) { throw new Exception( "Rack holds serial '{$source_stock['serial_number']}', not '{$data['serial_number']}'." ); } // Quantity comes from the source stock_in row, NOT user input. // Under 1:1, taking from this rack = taking the full batch. $quantity = (int) $source_stock['in']; $ref_id = (int) $source_stock['id']; $lot_number = $source_stock['lot_number'] ?? null; $serial_number = $source_stock['serial_number'] ?? null; // INSERT new stock_out record. // lot_number and serial_number carried from source stock_in row. // ref_id links this stock_out row back to the stock_in batch it consumed. $sql = "INSERT INTO `$table` (uuid, company_id, `date`, product_sku, `out`, zone, aisle, rack, contact_id, `description`, `log`, `type`, ref_id, lot_number, serial_number) VALUES (:uuid, :company_id, :date, :product_sku, :quantity, :zone, :aisle, :rack, :contact_id, :description, :log, 'out', :ref_id, :lot_number, :serial_number)"; $sth = $pdo->prepare($sql); $sth->execute([ ":uuid" => $uuid, ":company_id" => $company_id, ":date" => date('Y-m-d H:i:s'), ":product_sku" => $data["product_sku"], ":quantity" => $quantity, ":zone" => $data["zone"], ":aisle" => $data["aisle"], ":rack" => $data["rack"], ":contact_id" => (int)$data["contact_id"], ":description" => $data["description"], ":log" => json_encode($table_log), ":ref_id" => $ref_id, ":lot_number" => $lot_number, ":serial_number" => $serial_number, ]); // Release the rack — stock is now gone from this location. $whMgmt->releaseRack( $warehouse_id, $data["zone"], $data["aisle"], $data["rack"] ); // Balance: total_out increases by delta. // INSERT: $row['out'] = 0 → delta = full quantity // UPDATE: $row['out'] = old → delta = new - old $whMgmt->adjustBalance( 'out', $warehouse_id, $data["product_sku"], $row['out'] ?? 0, $quantity ); } }); $answer["success"] = 1; } 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)); ?>