diff --git a/app.zip b/app.zip index 6d23753..c39ea11 100644 Binary files a/app.zip and b/app.zip differ diff --git a/app/assets/utils/classes/ContactManager.php b/app/assets/utils/classes/ContactManager.php new file mode 100644 index 0000000..e301e2a --- /dev/null +++ b/app/assets/utils/classes/ContactManager.php @@ -0,0 +1,185 @@ +pdo = $pdo; + $this->company_id = $company_id; + } + + // ───────────────────────────────────────────────────────────── + // Private helpers + // ───────────────────────────────────────────────────────────── + + /** + * Check if a contact_id is referenced in any active td_stock_* row. + * Returns true if blocking stock exists. + */ + private function hasActiveStock(int $contact_id): bool { + + $sth = $this->pdo->prepare( + "SELECT table_name FROM information_schema.tables + WHERE table_schema = DATABASE() + AND table_name LIKE 'td_stock_%'" + ); + $sth->execute(); + $tables = $sth->fetchAll(PDO::FETCH_COLUMN); + + foreach ($tables as $table) { + $sth = $this->pdo->prepare( + "SELECT COUNT(*) FROM `$table` + WHERE company_id = :company_id + AND contact_id = :contact_id" + ); + $sth->execute([ + ':company_id' => $this->company_id, + ':contact_id' => $contact_id, + ]); + if ($sth->fetchColumn() > 0) { + return true; + } + } + + return false; + } + + /** + * Build a log entry array for audit context. + */ + private function buildLogEntry(string $action): array { + return [ + '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, + ]; + } + + // ───────────────────────────────────────────────────────────── + // Contact type + // ───────────────────────────────────────────────────────────── + + /** + * Soft-delete a contact type. + * + * Blocks if any md_contact references this type. + * + * @throws Exception if contact type not found or has dependent contacts + */ + public function deleteContactType(int $type_id): void { + + $sth = $this->pdo->prepare( + "SELECT id, contact_type, `log` FROM md_contact_type + WHERE company_id = :company_id AND id = :id" + ); + $sth->execute([ + ':company_id' => $this->company_id, + ':id' => $type_id, + ]); + $row = $sth->fetch(PDO::FETCH_ASSOC); + + if (!$row) { + throw new Exception("Contact type not found."); + } + + // Block if any contact references this type + $sth = $this->pdo->prepare( + "SELECT COUNT(*) FROM md_contact + WHERE company_id = :company_id + AND contact_type = :type_id" + ); + $sth->execute([ + ':company_id' => $this->company_id, + ':type_id' => $type_id, + ]); + + if ($sth->fetchColumn() > 0) { + throw new Exception( + "Cannot delete — contact type \"{$row['contact_type']}\" " . + "still has contacts assigned to it." + ); + } + + // Append delete event to log + $log = json_decode($row['log'] ?? '[]', true) ?: []; + $log[] = $this->buildLogEntry('delete'); + + // Soft-delete + $this->pdo->prepare( + "UPDATE md_contact_type + SET company_id = company_id * -1, + `log` = :log + WHERE id = :id AND company_id = :company_id" + )->execute([ + ':log' => json_encode($log), + ':id' => $type_id, + ':company_id' => $this->company_id, + ]); + } + + // ───────────────────────────────────────────────────────────── + // Contact + // ───────────────────────────────────────────────────────────── + + /** + * Soft-delete a contact. + * + * Blocks if the contact is referenced in any active td_stock_* row. + * + * @throws Exception if contact not found or referenced in active stock + */ + public function deleteContact(int $contact_id): void { + + $sth = $this->pdo->prepare( + "SELECT id, contact_name, `log` FROM md_contact + WHERE company_id = :company_id AND id = :id" + ); + $sth->execute([ + ':company_id' => $this->company_id, + ':id' => $contact_id, + ]); + $row = $sth->fetch(PDO::FETCH_ASSOC); + + if (!$row) { + throw new Exception("Contact not found."); + } + + // Block if contact is referenced in any active stock transaction + if ($this->hasActiveStock($contact_id)) { + throw new Exception( + "Cannot delete — \"{$row['contact_name']}\" " . + "is referenced in active stock transactions." + ); + } + + // Append delete event to log + $log = json_decode($row['log'] ?? '[]', true) ?: []; + $log[] = $this->buildLogEntry('delete'); + + // Soft-delete + $this->pdo->prepare( + "UPDATE md_contact + SET company_id = company_id * -1, + `log` = :log + WHERE id = :id AND company_id = :company_id" + )->execute([ + ':log' => json_encode($log), + ':id' => $contact_id, + ':company_id' => $this->company_id, + ]); + } +} \ No newline at end of file diff --git a/app/assets/utils/classes/ProductManager.php b/app/assets/utils/classes/ProductManager.php new file mode 100644 index 0000000..e6207c8 --- /dev/null +++ b/app/assets/utils/classes/ProductManager.php @@ -0,0 +1,186 @@ +pdo = $pdo; + $this->company_id = $company_id; + } + + // ───────────────────────────────────────────────────────────── + // Private helpers + // ───────────────────────────────────────────────────────────── + + /** + * Check if a SKU has any active stock across all td_stock_* tables. + * Returns the first blocking table name found, or null if clear. + */ + private function findActiveStock(string $sku): ?string { + + $sth = $this->pdo->prepare( + "SELECT table_name FROM information_schema.tables + WHERE table_schema = DATABASE() + AND table_name LIKE 'td_stock_%'" + ); + $sth->execute(); + $tables = $sth->fetchAll(PDO::FETCH_COLUMN); + + foreach ($tables as $table) { + $sth = $this->pdo->prepare( + "SELECT COUNT(*) FROM `$table` + WHERE company_id = :company_id + AND product_sku = :sku" + ); + $sth->execute([ + ':company_id' => $this->company_id, + ':sku' => $sku, + ]); + if ($sth->fetchColumn() > 0) { + return $table; + } + } + + return null; + } + + /** + * Build a log entry array for audit context. + */ + private function buildLogEntry(string $action): array { + return [ + '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, + ]; + } + + // ───────────────────────────────────────────────────────────── + // Product category + // ───────────────────────────────────────────────────────────── + + /** + * Soft-delete a product category. + * + * Blocks if any md_product references this category. + * + * @throws Exception if category not found or has dependent products + */ + public function deleteCategory(int $category_id): void { + + $sth = $this->pdo->prepare( + "SELECT id, category, `log` FROM md_product_category + WHERE company_id = :company_id AND id = :id" + ); + $sth->execute([ + ':company_id' => $this->company_id, + ':id' => $category_id, + ]); + $row = $sth->fetch(PDO::FETCH_ASSOC); + + if (!$row) { + throw new Exception("Product category not found."); + } + + // Block if any product references this category + $sth = $this->pdo->prepare( + "SELECT COUNT(*) FROM md_product + WHERE company_id = :company_id + AND category = :category_id" + ); + $sth->execute([ + ':company_id' => $this->company_id, + ':category_id' => $category_id, + ]); + + if ($sth->fetchColumn() > 0) { + throw new Exception( + "Cannot delete — category \"{$row['category']}\" " . + "still has products assigned to it." + ); + } + + // Append delete event to log + $log = json_decode($row['log'] ?? '[]', true) ?: []; + $log[] = $this->buildLogEntry('delete'); + + // Soft-delete + $this->pdo->prepare( + "UPDATE md_product_category + SET company_id = company_id * -1, + `log` = :log + WHERE id = :id AND company_id = :company_id" + )->execute([ + ':log' => json_encode($log), + ':id' => $category_id, + ':company_id' => $this->company_id, + ]); + } + + // ───────────────────────────────────────────────────────────── + // Product + // ───────────────────────────────────────────────────────────── + + /** + * Soft-delete a product. + * + * Blocks if the product SKU has any active stock across any td_stock_* table. + * + * @throws Exception if product not found or has active stock + */ + public function deleteProduct(int $product_id): void { + + $sth = $this->pdo->prepare( + "SELECT id, product_name, sku, `log` FROM md_product + WHERE company_id = :company_id AND id = :id" + ); + $sth->execute([ + ':company_id' => $this->company_id, + ':id' => $product_id, + ]); + $row = $sth->fetch(PDO::FETCH_ASSOC); + + if (!$row) { + throw new Exception("Product not found."); + } + + // Block if active stock exists for this SKU in any warehouse + $blocking_table = $this->findActiveStock($row['sku']); + if ($blocking_table !== null) { + throw new Exception( + "Cannot delete — \"{$row['product_name']}\" " . + "still has active stock in the system." + ); + } + + // Append delete event to log + $log = json_decode($row['log'] ?? '[]', true) ?: []; + $log[] = $this->buildLogEntry('delete'); + + // Soft-delete + $this->pdo->prepare( + "UPDATE md_product + SET company_id = company_id * -1, + `log` = :log + WHERE id = :id AND company_id = :company_id" + )->execute([ + ':log' => json_encode($log), + ':id' => $product_id, + ':company_id' => $this->company_id, + ]); + } +} \ No newline at end of file diff --git a/app/assets/utils/classes/WarehouseManager.php b/app/assets/utils/classes/WarehouseManager.php index e06656b..76ff906 100644 --- a/app/assets/utils/classes/WarehouseManager.php +++ b/app/assets/utils/classes/WarehouseManager.php @@ -125,8 +125,22 @@ class WarehouseManager { */ public function syncRacks(int $storage_id, array $range): void { + // Guard: refuse if any rack under this storage_id is still occupied $this->validateRangeChange($storage_id, $range); - $this->removeRacksOutsideRange($storage_id, $range); + + // Wipe all empty racks belonging to this storage_id + $sql = "DELETE FROM md_rack + WHERE company_id = :company_id + AND storage_id = :storage_id + AND product_sku IS NULL"; + + $sth = $this->pdo->prepare($sql); + $sth->execute([ + ':company_id' => $this->company_id, + ':storage_id' => $storage_id, + ]); + + // Reinsert the full range fresh $this->insertRacksInRange($storage_id, $range); } @@ -161,32 +175,6 @@ class WarehouseManager { } } - /** - * Delete empty racks that fall outside the new range. - */ - private function removeRacksOutsideRange(int $storage_id, array $range): void { - - $sql = "DELETE FROM md_rack - WHERE company_id = :company_id - AND storage_id = :storage_id - AND ( - CAST(aisle AS UNSIGNED) < :aisle_from OR - CAST(aisle AS UNSIGNED) > :aisle_to OR - CAST(rack AS UNSIGNED) < :rack_from OR - CAST(rack AS UNSIGNED) > :rack_to - )"; - - $sth = $this->pdo->prepare($sql); - $sth->execute([ - ":company_id" => $this->company_id, - ":storage_id" => $storage_id, - ":aisle_from" => (int) $range['aisle_from'], - ":aisle_to" => (int) $range['aisle_to'], - ":rack_from" => (int) $range['rack_from'], - ":rack_to" => (int) $range['rack_to'], - ]); - } - /** * Insert rack rows for the full range (IGNORE skips duplicates). */ @@ -266,24 +254,30 @@ class WarehouseManager { // ───────────────────────────────────────────────────────────── /** - * 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, + * Insert a log entry into md_rack_log for the given rack and action. + * Captures user_id, dt (now), and login (session start) for audit context, * plus any action-specific fields passed via $extra. */ - private function buildRackLog(?string $existing_log, string $action, array $extra = []): string { + private function insertRackLog(int $rack_id, string $action, array $extra = []): void { - $log = json_decode($existing_log ?: '[]', true) ?: []; + $sql = "INSERT INTO md_rack_log + (company_id, md_rack_id, user_id, dt, login, action, product_sku, td_stock_id) + VALUES + (:company_id, :md_rack_id, :user_id, :dt, :login, :action, :product_sku, :td_stock_id)"; - $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); + $sth = $this->pdo->prepare($sql); + $sth->execute([ + ':company_id' => $this->company_id, + ':md_rack_id' => $rack_id, + ':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, + ':product_sku' => $extra['product_sku'] ?? null, + ':td_stock_id' => $extra['td_stock_id'] ?? null, + ]); } /** @@ -296,8 +290,8 @@ class WarehouseManager { */ 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 + + $sql = "SELECT id, product_sku FROM md_rack WHERE company_id = :company_id AND warehouse = :warehouse AND zone = :zone @@ -328,20 +322,20 @@ class WarehouseManager { // Single atomic UPDATE: state + log together $sql = "UPDATE md_rack SET product_sku = :product_sku, - td_stock_id = :td_stock_id, - `log` = :log + td_stock_id = :td_stock_id WHERE id = :id"; $sth = $this->pdo->prepare($sql); $sth->execute([ ":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'], ]); + + $this->insertRackLog($current['id'], 'occupy', [ + 'product_sku' => $product_sku, + 'td_stock_id' => $td_stock_id, + ]); } /** @@ -349,8 +343,8 @@ class WarehouseManager { */ public function releaseRack($warehouse_id, $zone, $aisle, $rack): void { - // Fetch current state + log (needed to append release event) - $sql = "SELECT id, product_sku, td_stock_id, `log` FROM md_rack + + $sql = "SELECT id, product_sku, td_stock_id FROM md_rack WHERE company_id = :company_id AND warehouse = :warehouse AND zone = :zone @@ -376,18 +370,18 @@ class WarehouseManager { // Clear state + record log in one UPDATE $sql = "UPDATE md_rack SET product_sku = NULL, - td_stock_id = NULL, - `log` = :log + td_stock_id = NULL 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'], ]); + + $this->insertRackLog($current['id'], 'release', [ + 'product_sku' => $current['product_sku'], + 'td_stock_id' => $current['td_stock_id'], + ]); } /** @@ -403,7 +397,7 @@ class WarehouseManager { ): void { // Fetch both racks with FOR UPDATE, ordered by id to prevent deadlocks - $sql = "SELECT id, warehouse, zone, aisle, rack, product_sku, td_stock_id, `log` + $sql = "SELECT id, warehouse, zone, aisle, rack, product_sku, td_stock_id FROM md_rack WHERE company_id = :company_id AND ( @@ -464,32 +458,443 @@ class WarehouseManager { // Clear source — state + log in one UPDATE $sql = "UPDATE md_rack - SET product_sku = NULL, td_stock_id = NULL, `log` = :log + SET product_sku = NULL, td_stock_id = NULL WHERE id = :id"; $this->pdo->prepare($sql)->execute([ - ":log" => $this->buildRackLog($from['log'], 'transfer_out', [ - 'product_sku' => $sku, - 'td_stock_id' => $td_stock_id, - 'to_rack_id' => $to['id'], - ]), ":id" => $from['id'], ]); + $this->insertRackLog($from['id'], 'transfer_out', [ + 'product_sku' => $sku, + 'td_stock_id' => $td_stock_id, + ]); + // Populate destination — state + log in one UPDATE $sql = "UPDATE md_rack - SET product_sku = :sku, td_stock_id = :td_stock_id, `log` = :log + SET product_sku = :sku, td_stock_id = :td_stock_id 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'], ]); + + $this->insertRackLog($to['id'], 'transfer_in', [ + 'product_sku' => $sku, + 'td_stock_id' => $td_stock_id, + ]); } + + + // ───────────────────────────────────────────────────────────── + // Delete (soft-delete) operations + // ───────────────────────────────────────────────────────────── + + /** + * Validate that a given row is the latest transaction for its SKU in the table. + * Throws a descriptive exception if a newer transaction exists. + * + * @param string $product_sku SKU to check + * @param string $row_date The date of the row being deleted + * @param string $product_name Human-readable product name for error message + * @throws Exception if a newer transaction exists + */ + private function validateLatestTransaction( + string $product_sku, + string $row_date, + string $product_name + ): void { + + // Discover all td_stock_* tables for this database + $sth = $this->pdo->prepare( + "SELECT table_name FROM information_schema.tables + WHERE table_schema = DATABASE() + AND table_name LIKE 'td_stock_%'" + ); + $sth->execute(); + $tables = $sth->fetchAll(PDO::FETCH_COLUMN); + + if (empty($tables)) { + return; + } + + // Build UNION across all td_stock_* tables to find global MAX(date) + $unions = implode(' UNION ALL ', array_map( + fn($t) => "SELECT `type`, `date` FROM `$t` + WHERE company_id = :company_id + AND product_sku = :product_sku", + $tables + )); + + $sth = $this->pdo->prepare( + "SELECT `type`, `date` FROM ($unions) AS all_stock + ORDER BY `date` DESC + LIMIT 1" + ); + $sth->execute([ + ':company_id' => $this->company_id, + ':product_sku' => $product_sku, + ]); + $latest = $sth->fetch(PDO::FETCH_ASSOC); + + if (!$latest || $row_date === $latest['date']) { + return; // ✓ this is the latest globally — allow + } + + $type = strtoupper($latest['type']); + $date = $latest['date']; + + throw new Exception( + "Cannot delete — \"{$product_name}\" has a newer " . + "{$type} transaction on {$date} that must be deleted first." + ); + } + + /** + * Helper to build a log entry with consistent user and timestamp info for warehouse operations. + */ + private function buildLogEntry(string $action): array { + return [ + '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, + ]; + } + + /** + * Resolve product name from md_product for readable error messages. + */ + private function getProductName(string $product_sku): string { + + $sth = $this->pdo->prepare( + "SELECT product_name FROM md_product + WHERE company_id = :company_id AND sku = :sku" + ); + $sth->execute([ + ':company_id' => $this->company_id, + ':sku' => $product_sku, + ]); + return $sth->fetchColumn() ?: $product_sku; + } + + /** + * Soft-delete a stock_in record and reverse its side effects. + * + * - Validates this is the latest transaction for the SKU + * - Negates company_id (soft-delete) + * - Releases the rack + * - Reverses the balance + * + * Must be called inside a DB transaction by the caller. + * + * @throws Exception if not the latest transaction or record not found + */ + public function deleteStockIn(int $stock_id, int $warehouse_id): void { + + $ctx = $this->getStockContext($warehouse_id, $stock_id); + $table = $ctx['table']; + $row = $ctx['row']; + + if (!$row) { + throw new Exception("Stock in record not found."); + } + + $product_sku = $row['product_sku']; + $product_name = $this->getProductName($product_sku); + + $this->validateLatestTransaction($product_sku, $row['date'], $product_name); + + // Soft-delete + $this->pdo->prepare( + "UPDATE `$table` + SET company_id = company_id * -1 + WHERE id = :id AND company_id = :company_id" + )->execute([':id' => $stock_id, ':company_id' => $this->company_id]); + + // Release the rack + $this->releaseRack($warehouse_id, $row['zone'], $row['aisle'], $row['rack']); + + // Reverse balance + $this->adjustBalance('in', $warehouse_id, $product_sku, (int)$row['in'], 0); + } + + /** + * Soft-delete a stock_out record and reverse its side effects. + * + * - Validates this is the latest transaction for the SKU + * - Negates company_id (soft-delete) + * - Re-occupies the rack with the original stock_in batch (via ref_id) + * - Reverses the balance + * + * Must be called inside a DB transaction by the caller. + * + * @throws Exception if not the latest transaction or record not found + */ + public function deleteStockOut(int $stock_id, int $warehouse_id): void { + + $ctx = $this->getStockContext($warehouse_id, $stock_id); + $table = $ctx['table']; + $row = $ctx['row']; + + if (!$row) { + throw new Exception("Stock out record not found."); + } + + $product_sku = $row['product_sku']; + $product_name = $this->getProductName($product_sku); + + $this->validateLatestTransaction($product_sku, $row['date'], $product_name); + + // Soft-delete + $this->pdo->prepare( + "UPDATE `$table` + SET company_id = company_id * -1 + WHERE id = :id AND company_id = :company_id" + )->execute([':id' => $stock_id, ':company_id' => $this->company_id]); + + // Re-occupy the rack — restore the stock_in batch that was consumed + $this->occupyRack( + $warehouse_id, + $row['zone'], $row['aisle'], $row['rack'], + $product_sku, + (int)$row['ref_id'] + ); + + // Reverse balance + $this->adjustBalance('out', $warehouse_id, $product_sku, (int)$row['out'], 0); + } + + /** + * Soft-delete a transfer record pair and reverse all side effects. + * + * - Validates this is the latest transaction for the SKU on BOTH warehouses + * - Negates company_id on both rows (soft-delete) + * - Releases destination rack, re-occupies source rack + * - Reverses balances on both warehouses + * + * Must be called inside a DB transaction by the caller. + * + * @throws Exception if not the latest transaction on either side, or records not found + */ + public function deleteTransfer(int $from_stock_id, int $from_warehouse_id): void { + + // Fetch the "from" row (transfer_out side) + $from_ctx = $this->getStockContext($from_warehouse_id, $from_stock_id); + $from_table = $from_ctx['table']; + $from_row = $from_ctx['row']; + + if (!$from_row || $from_row['type'] !== 'transfer') { + throw new Exception("Transfer record not found."); + } + + $product_sku = $from_row['product_sku']; + $product_name = $this->getProductName($product_sku); + $to_warehouse = (int)$from_row['ref_warehouse']; + $ref_id = (int)$from_row['ref_id']; + + // Fetch the "to" row (transfer_in side) + $to_ctx = $this->getStockContext($to_warehouse, $ref_id); + $to_row = $to_ctx['row']; + + if (!$to_row) { + throw new Exception("Paired destination record missing — data integrity issue."); + } + + // Single global validation covers both warehouses + $this->validateLatestTransaction($product_sku, $from_row['date'], $product_name); + + // Soft-delete both rows + $this->pdo->prepare( + "UPDATE `$from_table` + SET company_id = company_id * -1 + WHERE id = :id AND company_id = :company_id" + )->execute([':id' => $from_stock_id, ':company_id' => $this->company_id]); + + $this->pdo->prepare( + "UPDATE `{$to_ctx['table']}` + SET company_id = company_id * -1 + WHERE id = :id AND company_id = :company_id" + )->execute([':id' => $ref_id, ':company_id' => $this->company_id]); + + // Destination rack was occupied by transfer_in — release it + $this->releaseRack( + $to_warehouse, + $to_row['zone'], $to_row['aisle'], $to_row['rack'] + ); + + // Source rack was released by transfer_out — re-occupy with original batch + $this->occupyRack( + $from_warehouse_id, + $from_row['zone'], $from_row['aisle'], $from_row['rack'], + $product_sku, + $from_stock_id + ); + + // Reverse balances + $quantity = (int)$from_row['out']; + $this->adjustBalance('out', $from_warehouse_id, $product_sku, $quantity, 0); + $this->adjustBalance('in', $to_warehouse, $product_sku, $quantity, 0); + } + + /** + * Soft-delete a warehouse. + * + * Blocks if any md_storage references this warehouse by name. + * + * Must be called inside a DB transaction by the caller. + * + * @throws Exception if warehouse not found or has dependent storage + */ + public function deleteWarehouse(int $warehouse_id): void { + + $sth = $this->pdo->prepare( + "SELECT id, warehouse_name, `log` FROM md_warehouse + WHERE company_id = :company_id AND id = :id" + ); + $sth->execute([ + ':company_id' => $this->company_id, + ':id' => $warehouse_id, + ]); + $row = $sth->fetch(PDO::FETCH_ASSOC); + + if (!$row) { + throw new Exception("Warehouse not found."); + } + + // Block if any md_storage references this warehouse by name + $sth = $this->pdo->prepare( + "SELECT COUNT(*) FROM md_storage + WHERE company_id = :company_id + AND warehouse = :warehouse_name" + ); + $sth->execute([ + ':company_id' => $this->company_id, + ':warehouse_name' => $row['warehouse_name'], + ]); + + if ($sth->fetchColumn() > 0) { + throw new Exception( + "Cannot delete — warehouse \"{$row['warehouse_name']}\" " . + "still has storage locations assigned to it." + ); + } + + // Append delete event to log + $log = json_decode($row['log'] ?? '[]', true) ?: []; + $log[] = $this->buildLogEntry('delete'); + + // Soft-delete + $this->pdo->prepare( + "UPDATE md_warehouse + SET company_id = company_id * -1, + `log` = :log + WHERE id = :id AND company_id = :company_id" + )->execute([ + ':log' => json_encode($log), + ':id' => $warehouse_id, + ':company_id' => $this->company_id, + ]); + } + + + /** + * Soft-delete a storage record and its associated racks. + * + * - Blocks if any md_rack under this storage_id is occupied + * - Hard-deletes md_rack_log rows for those racks + * - Soft-deletes all md_rack rows under this storage_id + * - Soft-deletes md_storage + * + * Must be called inside a DB transaction by the caller. + * + * @throws Exception if storage not found or has occupied racks + */ + public function deleteStorage(int $storage_id): void { + + $sth = $this->pdo->prepare( + "SELECT id, zone, `log` FROM md_storage + WHERE company_id = :company_id AND id = :id" + ); + $sth->execute([ + ':company_id' => $this->company_id, + ':id' => $storage_id, + ]); + $row = $sth->fetch(PDO::FETCH_ASSOC); + + if (!$row) { + throw new Exception("Storage not found."); + } + + // Block if any rack under this storage is occupied + $sth = $this->pdo->prepare( + "SELECT COUNT(*) FROM md_rack + WHERE company_id = :company_id + AND storage_id = :storage_id + AND product_sku IS NOT NULL" + ); + $sth->execute([ + ':company_id' => $this->company_id, + ':storage_id' => $storage_id, + ]); + + if ($sth->fetchColumn() > 0) { + throw new Exception( + "Cannot delete — storage zone \"{$row['zone']}\" " . + "still has occupied racks with stock." + ); + } + + // Collect rack IDs before deleting — needed for rack log cleanup + $sth = $this->pdo->prepare( + "SELECT id FROM md_rack + WHERE company_id = :company_id + AND storage_id = :storage_id" + ); + $sth->execute([ + ':company_id' => $this->company_id, + ':storage_id' => $storage_id, + ]); + $rack_ids = $sth->fetchAll(PDO::FETCH_COLUMN); + + // Hard-delete md_rack_log for these racks + if (!empty($rack_ids)) { + $placeholders = implode(',', array_fill(0, count($rack_ids), '?')); + $this->pdo->prepare( + "DELETE FROM md_rack_log WHERE md_rack_id IN ($placeholders)" + )->execute($rack_ids); + } + + // Soft-delete all md_rack rows under this storage + $this->pdo->prepare( + "UPDATE md_rack + SET company_id = company_id * -1 + WHERE company_id = :company_id + AND storage_id = :storage_id" + )->execute([ + ':company_id' => $this->company_id, + ':storage_id' => $storage_id, + ]); + + // Append delete event to log + $log = json_decode($row['log'] ?? '[]', true) ?: []; + $log[] = $this->buildLogEntry('delete'); + + // Soft-delete md_storage + $this->pdo->prepare( + "UPDATE md_storage + SET company_id = company_id * -1, + `log` = :log + WHERE id = :id AND company_id = :company_id" + )->execute([ + ':log' => json_encode($log), + ':id' => $storage_id, + ':company_id' => $this->company_id, + ]); + } + } ?> \ No newline at end of file diff --git a/app/contact/api/engine/remove_contact.php b/app/contact/api/engine/remove_contact.php new file mode 100644 index 0000000..28dc3c9 --- /dev/null +++ b/app/contact/api/engine/remove_contact.php @@ -0,0 +1,29 @@ +deleteContact((int)$data['id']); + + }); + + $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)); +?> \ No newline at end of file diff --git a/app/contact/api/engine/remove_contact_type.php b/app/contact/api/engine/remove_contact_type.php new file mode 100644 index 0000000..6e89e9e --- /dev/null +++ b/app/contact/api/engine/remove_contact_type.php @@ -0,0 +1,29 @@ +deleteContactType((int)$data['id']); + + }); + + $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)); +?> \ No newline at end of file diff --git a/app/contact/contact.php b/app/contact/contact.php index 7db527c..aebf043 100644 --- a/app/contact/contact.php +++ b/app/contact/contact.php @@ -268,7 +268,7 @@ - + `; }) @@ -331,7 +331,7 @@ - + `; }) @@ -340,6 +340,48 @@ } + + function remove_contact_type(id,element) { + + return ajax_request({ + url: "contact/api/engine/remove_contact_type.php", + autoPrepare: true, + checkRequired: 0, + debugMode: 0, + data: { + id: id + }, + action: 'delete', + onSuccess: function(res) { + + element.parent().parent().remove(); + + } + }); + + } + + + function remove_contact(id,element) { + + return ajax_request({ + url: "contact/api/engine/remove_contact.php", + autoPrepare: true, + checkRequired: 0, + debugMode: 0, + data: { + id: id + }, + action: 'delete', + onSuccess: function(res) { + + element.parent().parent().remove(); + + } + }); + + } + $(async function() { try { await retrieve_contact_type(); diff --git a/app/ics/api/engine/delete_stock_in.php b/app/ics/api/engine/delete_stock_in.php new file mode 100644 index 0000000..d4e2bbb --- /dev/null +++ b/app/ics/api/engine/delete_stock_in.php @@ -0,0 +1,32 @@ +deleteStockIn( + (int)$data['id'], + (int)$data['wh'] + ); + + }); + + $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)); +?> \ No newline at end of file diff --git a/app/ics/api/engine/delete_stock_out.php b/app/ics/api/engine/delete_stock_out.php new file mode 100644 index 0000000..2f19b97 --- /dev/null +++ b/app/ics/api/engine/delete_stock_out.php @@ -0,0 +1,32 @@ +deleteStockOut( + (int)$data['id'], + (int)$data['wh'] + ); + + }); + + $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)); +?> \ No newline at end of file diff --git a/app/ics/api/engine/delete_stock_transfer.php b/app/ics/api/engine/delete_stock_transfer.php new file mode 100644 index 0000000..c211a84 --- /dev/null +++ b/app/ics/api/engine/delete_stock_transfer.php @@ -0,0 +1,32 @@ +deleteTransfer( + (int)$data['id'], + (int)$data['wh'] + ); + + }); + + $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)); +?> \ No newline at end of file diff --git a/app/ics/api/engine/manage_stock_in.php b/app/ics/api/engine/manage_stock_in.php index 4558a06..77564b3 100644 --- a/app/ics/api/engine/manage_stock_in.php +++ b/app/ics/api/engine/manage_stock_in.php @@ -24,11 +24,6 @@ // 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, `description` = :description, `log` = :log @@ -38,41 +33,12 @@ $sth->execute([ ":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"], ":description" => $data["description"], ":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 { @@ -107,17 +73,17 @@ $data['product_sku'], $td_stock_id ); - } + // Balance adjustment handles both INSERT (delta = full qty) + // and UPDATE (delta = new - old) via the same formula. + $whMgmt->adjustBalance( + 'in', + $warehouse_id, + $data["product_sku"], + $row['in'] ?? 0, + $data["quantity"] + ); - // 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"] - ); + } }); diff --git a/app/ics/api/engine/manage_stock_out.php b/app/ics/api/engine/manage_stock_out.php index b8e4d68..705ca13 100644 --- a/app/ics/api/engine/manage_stock_out.php +++ b/app/ics/api/engine/manage_stock_out.php @@ -19,42 +19,10 @@ $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_ 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, `description` = :description, `log` = :log @@ -64,41 +32,39 @@ $sth->execute([ ":id" => $id, ":company_id" => $company_id, - ":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), ]); - // Only touch md_rack if the physical location actually changed. - $rack_changed = ( - ($row['zone'] ?? '') != $data['zone'] || - ($row['aisle'] ?? '') != $data['aisle'] || - ($row['rack'] ?? '') != $data['rack'] + } 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 ($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'] + if (!$source_stock) { + throw new Exception( + "Rack {$data['zone']}-{$data['aisle']}-{$data['rack']} is empty — nothing to take out." ); } - } else { + // 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']; // INSERT new stock_out record. // ref_id links this stock_out row back to the stock_in batch it consumed. @@ -130,18 +96,18 @@ $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 + ); - // 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 - ); + } }); diff --git a/app/ics/api/engine/retrieve_stock_in.php b/app/ics/api/engine/retrieve_stock_in.php index b2add0f..c66cf06 100644 --- a/app/ics/api/engine/retrieve_stock_in.php +++ b/app/ics/api/engine/retrieve_stock_in.php @@ -31,7 +31,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 diff --git a/app/ics/api/engine/retrieve_stock_out.php b/app/ics/api/engine/retrieve_stock_out.php index 40e359d..68b43b5 100644 --- a/app/ics/api/engine/retrieve_stock_out.php +++ b/app/ics/api/engine/retrieve_stock_out.php @@ -31,7 +31,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 diff --git a/app/ics/manage_stock_in.php b/app/ics/manage_stock_in.php index 8425248..35f210e 100644 --- a/app/ics/manage_stock_in.php +++ b/app/ics/manage_stock_in.php @@ -273,6 +273,8 @@ $('#product_name').val(data.product_name); $('#product_sku').attr('secondary', data.product_sku); + $('#product_sku,#quantity').prop('disabled', true); + $(`button[type=submit]`).text('Update'); $(`button[type=reset]`).hide(); diff --git a/app/ics/manage_stock_out.php b/app/ics/manage_stock_out.php index 40383b7..8f6293b 100644 --- a/app/ics/manage_stock_out.php +++ b/app/ics/manage_stock_out.php @@ -271,6 +271,8 @@ $('#product_name').val(data.product_name); $('#product_sku').attr('secondary', data.product_sku); + $('#product_sku,#quantity').prop('disabled', true); + $(`button[type=submit]`).text('Update'); $(`button[type=reset]`).hide(); diff --git a/app/ics/manage_stock_transfer.php b/app/ics/manage_stock_transfer.php index 4e4ed1d..1376d8d 100644 --- a/app/ics/manage_stock_transfer.php +++ b/app/ics/manage_stock_transfer.php @@ -344,10 +344,12 @@ $('#contact').val(data.contact_name) .attr('secondary', data.contact_name) .attr('data-id', data.contact_id); - console.log(data); + $('#product_name').val(data.product_name); $('#product_sku').attr('secondary', data.product_sku); + $('#product_sku,#quantity').prop('disabled', true); + $(`button[type=submit]`).text('Update'); $(`button[type=reset]`).hide(); diff --git a/app/ics/stock_in.php b/app/ics/stock_in.php index 34fc53f..0e3c27a 100644 --- a/app/ics/stock_in.php +++ b/app/ics/stock_in.php @@ -133,7 +133,10 @@ - + + + `; }) @@ -162,6 +165,24 @@ } + function delete_stock_in(element, id) { + + return ajax_request({ + url: "ics/api/engine/delete_stock_in.php", + autoPrepare: true, + checkRequired: 0, + action: 'delete', + data : { + id: id, + wh: $(`select#warehouse`).val() + }, + onSuccess: function(res) { + element.closest('tr').remove(); + } + }); + + } + $(async function() { try { await retrieve_warehouse(); diff --git a/app/ics/stock_out.php b/app/ics/stock_out.php index 2883583..0c30699 100644 --- a/app/ics/stock_out.php +++ b/app/ics/stock_out.php @@ -133,7 +133,10 @@ - + + + `; }) @@ -162,6 +165,24 @@ } + function delete_stock_out(element, id) { + + return ajax_request({ + url: "ics/api/engine/delete_stock_out.php", + autoPrepare: true, + checkRequired: 0, + action: 'delete', + data : { + id: id, + wh: $(`select#warehouse`).val() + }, + onSuccess: function(res) { + element.closest('tr').remove(); + } + }); + + } + $(async function() { try { await retrieve_warehouse(); diff --git a/app/ics/stock_transfer.php b/app/ics/stock_transfer.php index 9a6a685..c1b3458 100644 --- a/app/ics/stock_transfer.php +++ b/app/ics/stock_transfer.php @@ -133,7 +133,10 @@ - + + + `; }) @@ -162,6 +165,26 @@ } + + + function delete_stock_transfer(element, id) { + + return ajax_request({ + url: "ics/api/engine/delete_stock_transfer.php", + autoPrepare: true, + checkRequired: 0, + action: 'delete', + data : { + id: id, + wh: $(`select#warehouse`).val() + }, + onSuccess: function(res) { + element.closest('tr').remove(); + } + }); + + } + $(async function() { try { await retrieve_warehouse(); diff --git a/app/inventory/api/engine/remove_category.php b/app/inventory/api/engine/remove_category.php index 0ed6092..27cf4c5 100644 --- a/app/inventory/api/engine/remove_category.php +++ b/app/inventory/api/engine/remove_category.php @@ -1,69 +1,18 @@ -deleteCategory((int)$data['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 - ]); - - $product_count = (int) $sth->fetchColumn(); - - 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 - ]); - - $table_log = json_decode($sth->fetchColumn() ?: '[]', true); - if (!is_array($table_log)) $table_log = []; - $table_log[] = $logging; - - // 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; diff --git a/app/inventory/api/engine/remove_product.php b/app/inventory/api/engine/remove_product.php new file mode 100644 index 0000000..630337d --- /dev/null +++ b/app/inventory/api/engine/remove_product.php @@ -0,0 +1,29 @@ +deleteProduct((int)$data['id']); + + }); + + $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)); +?> \ No newline at end of file diff --git a/app/inventory/api/engine/remove_storage.php b/app/inventory/api/engine/remove_storage.php new file mode 100644 index 0000000..3e7f336 --- /dev/null +++ b/app/inventory/api/engine/remove_storage.php @@ -0,0 +1,29 @@ +deleteStorage((int)$data['id']); + + }); + + $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)); +?> \ No newline at end of file diff --git a/app/inventory/api/engine/remove_warehouse.php b/app/inventory/api/engine/remove_warehouse.php new file mode 100644 index 0000000..4926d82 --- /dev/null +++ b/app/inventory/api/engine/remove_warehouse.php @@ -0,0 +1,29 @@ +deleteWarehouse((int)$data['id']); + + }); + + $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)); +?> \ No newline at end of file diff --git a/app/inventory/product.php b/app/inventory/product.php index 68b2ea9..aae13d0 100644 --- a/app/inventory/product.php +++ b/app/inventory/product.php @@ -298,6 +298,28 @@ + function remove_product(id,element) { + + return ajax_request({ + url: "inventory/api/engine/remove_product.php", + autoPrepare: true, + checkRequired: 0, + debugMode: 0, + data: { + id: id + }, + action: 'delete', + onSuccess: function(res) { + + element.parent().parent().remove(); + + } + }); + + } + + + function change_page_category(page_num) { var offset = (page_num - 1) * prop_limit; @@ -382,7 +404,7 @@ - + `; }) diff --git a/app/inventory/warehouse.php b/app/inventory/warehouse.php index 666cdaa..58af3aa 100644 --- a/app/inventory/warehouse.php +++ b/app/inventory/warehouse.php @@ -335,7 +335,7 @@ - + `; }) @@ -393,7 +393,7 @@ - + `; }) @@ -402,6 +402,49 @@ } + + function remove_warehouse(id,element) { + + return ajax_request({ + url: "inventory/api/engine/remove_warehouse.php", + autoPrepare: true, + checkRequired: 0, + debugMode: 0, + data: { + id: id + }, + action: 'delete', + onSuccess: function(res) { + + element.parent().parent().remove(); + + } + }); + + } + + + function remove_storage(id,element) { + + return ajax_request({ + url: "inventory/api/engine/remove_storage.php", + autoPrepare: true, + checkRequired: 0, + debugMode: 0, + data: { + id: id + }, + action: 'delete', + onSuccess: function(res) { + + element.parent().parent().remove(); + + } + }); + + } + + $(async function() { try { await retrieve_stats();