<?php
require "global.ajax.php";

$sale_date = $currentDateTimeByZone;

switch ($_GET["op"]) {

    case 'ajaxSaveUpdate':

        // POST VARIABLES
        $sale_id = isset($_POST["sale_id"]) ? funCheckInput($_POST["sale_id"]) : "";
        $sale_ent_id = isset($_POST["sale_ent_id"]) ? funCheckInput($_POST["sale_ent_id"]) : "";
        $sale_pos_id = isset($_POST["sale_pos_id"]) ? funCheckInput($_POST["sale_pos_id"]) : "";
        $sale_ncf_type = isset($_POST["sale_ncf_type"]) ? funCheckInput($_POST["sale_ncf_type"]) : "";
        $sale_type = isset($_POST["sale_type"]) ? funCheckInput($_POST["sale_type"]) : "";
        $sale_cus_id = isset($_POST["sale_cus_id"]) ? funCheckInput($_POST["sale_cus_id"]) : "";
        $sale_table_id = isset($_POST["sale_table_id"]) ? funCheckInput($_POST["sale_table_id"]) : "";
        $sale_sub_total = isset($_POST["sale_sub_total"]) ? funCheckInput($_POST["sale_sub_total"]) : "";
        $sale_tax_percent = isset($_POST["sale_tax_percent"]) ? funCheckInput($_POST["sale_tax_percent"]) : "";
        $sale_tax_value = isset($_POST["sale_tax_value"]) ? funCheckInput($_POST["sale_tax_value"]) : "";
        $sale_tax_law_percent = isset($_POST["sale_tax_law_percent"]) ? funCheckInput($_POST["sale_tax_law_percent"]) : "";
        $sale_tax_law_value = isset($_POST["sale_tax_law_value"]) ? funCheckInput($_POST["sale_tax_law_value"]) : "";
        $sale_discount_percent = isset($_POST["sale_discount_percent"]) ? funCheckInput($_POST["sale_discount_percent"]) : "";
        $sale_discount_value = isset($_POST["sale_discount_value"]) ? funCheckInput($_POST["sale_discount_value"]) : "";
        $sale_cost_delivery = isset($_POST["sale_cost_delivery"]) ? funCheckInput($_POST["sale_cost_delivery"]) : "";
        $sale_type_tax_percent = isset($_POST["sale_type_tax_percent"]) ? funCheckInput($_POST["sale_type_tax_percent"]) : "";
        $sale_type_tax_value = isset($_POST["sale_type_tax_value"]) ? funCheckInput($_POST["sale_type_tax_value"]) : "";
        $sale_amount_final = isset($_POST["sale_amount_final"]) ? funCheckInput($_POST["sale_amount_final"]) : "";
        $sale_debt = isset($_POST["sale_debt"]) ? funCheckInput($_POST["sale_debt"]) : "";

        if ($sale_ent_id == "") $sale_ent_id = $session_user_ent_id;
        if ($sale_pos_id == "") $sale_pos_id = $session_user_pos_id;

        // -------------------------------------------------------------------------
        // STEP 1 — Validate required sale details and payments details (if provided)
        // -------------------------------------------------------------------------
        if (empty($_POST['sale_det_ord_det_code']) || !is_array($_POST['sale_det_ord_det_code'])) {
            $response = ["status" => "error", "message" => "{$translate['sale_details_are_required_and_must_be_a_non_empty_array']}."];
            echo json_encode($response);
            exit();
        }

        // Payments are optional but if present must be a valid non-empty array
        $hasPayments = isset($_POST['pay_det_income']) && is_array($_POST['pay_det_income']) && !empty($_POST['pay_det_income']);

        // Payments are optional — only validate if the key was actually submitted
        if ($hasPayments) {
            if (isset($_POST["pay_det_income"]) && (empty($_POST["pay_det_income"]) || !is_array($_POST["pay_det_income"]))) {
                $response = ["status" => "error", "message" => "{$translate['payment_details_must_be_a_non_empty_array_when_provided']}."];
                echo json_encode($response);
                exit();
            }
        }

        $isEdit = (int) $sale_id > 0 ? true : false;
        $editingSaleId = $isEdit ? (int) $sale_id : 0;

        try {
            // ---------------------------------------------------------------------
            // STEP 2 — Open transaction
            // ---------------------------------------------------------------------
            $pdo->beginTransaction();

            // ---------------------------------------------------------------------
            // STEP 3 — EDIT ONLY: fetch already-committed qty per product for THIS
            //          sale BEFORE deleting anything.
            //          These units are currently counted as consumed by
            //          getProductStockById(), so we add them back to get the true
            //          available stock for this edit — no extra caller field needed.
            // ---------------------------------------------------------------------
            $existingQtyMap = [];
            if ($isEdit) {
                $stmtExisting = $pdo->prepare(
                    "SELECT sale_det_pro_id, SUM(sale_det_pro_qty) AS total_qty
                 FROM tb_sales_details
                 WHERE sale_det_sale_id = :sale_id
                 GROUP BY sale_det_pro_id"
                );
                $stmtExisting->execute([':sale_id' => $editingSaleId]);
                foreach ($stmtExisting->fetchAll(PDO::FETCH_ASSOC) as $row) {
                    $existingQtyMap[(int) $row['sale_det_pro_id']] = (float) $row['total_qty'];
                }
            }

            // ---------------------------------------------------------------------
            // STEP 4 — Stock check WITH pessimistic lock (SELECT ... FOR UPDATE)
            //          Runs BEFORE any DELETE — old rows are still intact.
            //          In edit mode, alreadyCommitted neutralises the qty that
            //          getProductStockById() sees as consumed for this sale.
            // ---------------------------------------------------------------------
            $sqlLock  = "SELECT pro_id, pro_description FROM tb_products
                     WHERE pro_id = :pro_id AND pro_status = 'Enabled'
                     FOR UPDATE";
            $stmtLock = $pdo->prepare($sqlLock);

            // ---------------------------------------------------------------------
            $stockErrors = [];

            for ($row = 0; $row < count($_POST['sale_det_ord_det_code']); $row++) {

                if (!isset($_POST['sale_det_pro_id'][$row], $_POST['sale_det_pro_qty'][$row])) {
                    // Missing required fields is a hard stop — exit immediately
                    echo json_encode(['status' => 'error', 'message' => "Missing 'sale_det_pro_id' or 'sale_det_pro_qty' in details[{$row}]."]);
                    exit();
                }

                $productId  = (int)   $_POST['sale_det_pro_id'][$row];
                $requestQty = (float) $_POST['sale_det_pro_qty'][$row];

                // Acquire exclusive row lock on this product
                $stmtLock->execute([':pro_id' => $productId]);
                $proInfo = $stmtLock->fetch(PDO::FETCH_ASSOC);

                if (!$proInfo) {
                    // Product not found is a hard stop — no point continuing
                    echo json_encode(['status' => 'error', 'message' => "Product ID {$productId} (details[{$row}]) {$translate['was_not_found_or_is_disabled']}."]);
                    exit();
                }

                $proDescription = $proInfo['pro_description'];

                // Read stock AFTER the lock is held — result is now serialised
                $stockRow     = getProductStockById($productId);
                $currentStock = $stockRow['data']['pro_current_stock'] ?? null;

                if ($currentStock === null) {
                    // Cannot retrieve stock is a hard stop — data integrity issue
                    echo json_encode(['status' => 'error', 'message' => "{$translate['could_not_retrieve_stock_for']}: {$proDescription}."]);
                    exit();
                }

                // 'FULL' = Service product, no quantity limit
                if ($currentStock === 'FULL') {
                    continue;
                }

                $currentStock = (float) $currentStock;

                // Units already committed to THIS sale are counted as consumed by
                // getProductStockById() — add them back for the true available ceiling
                $alreadyCommitted = $existingQtyMap[$productId] ?? 0.0;
                $effectiveStock   = $currentStock + $alreadyCommitted;

                if ($requestQty > $effectiveStock) {
                    // Collect — do NOT exit, continue checking remaining products
                    $stockErrors[] = "{$proDescription} -> {$translate['requested']}: {$requestQty}, {$translate['available']}: {$currentStock}";
                }
            }

            // ── After the loop: if any stock errors were collected, return them all ────────
            if (!empty($stockErrors)) {
                echo json_encode([
                    'status'  => "warning",
                    'title'  => "{$translate['insufficient_stock_for']}:",
                    'message' => implode("\n", $stockErrors),
                    "redirect" => false
                ]);
                exit();
            }

            // ---------------------------------------------------------------------
            // STEP 5 — Insert / UPDATE tb_sales
            // ---------------------------------------------------------------------
            if ($isEdit) {

                $sale_ncf_type = "";
                $sale_ncf_number = "";

                $sale_info = getSaleData($sale_id);

                if ($sale_info['status'] == 'success') {
                    $sale_ncf_type = $sale_info['data']['sale_ncf_type'];
                    $sale_code = $sale_info['data']['sale_code'];
                    $sale_date = $sale_info['data']['sale_date'];
                    $sale_discount_old = $sale_info['data']['sale_discount_value'];
                    $sale_status = $sale_info['data']['sale_status'];
                } else {
                    $response = array("status" => "error", "message" => "{$translate['sale_not_found']} <br> {$sale_info['message']}");
                    echo json_encode($response);
                    exit();
                }

                if ($sale_status == "Disabled") {
                    $response = array("status" => "error", "message" => "{$translate['this_sale_has_been_cancelled_it_cannot_be_saved']}");
                    echo json_encode($response);
                    exit();
                }

                if (!empty(trim((string) $sale_ncf_type))) {
                    $sale_ncf_number = $sale_info['sale_ncf_number'];
                } else {

                    if (!empty(trim((string) $sale_ncf_type))) {
                        $sale_ncf_type = $sale_ncf_type;
                        $sale_ncf_number = generateFiscalReceipt($sale_ncf_type, $sale_ent_id, 'tb_sales', $sale_id);
                    }
                }

                $sqlUpdateSale = "UPDATE tb_sales SET
                    sale_ncf_type = :sale_ncf_type,
                    sale_ncf_number = :sale_ncf_number,
                    sale_sub_total = :sale_sub_total,
                    sale_tax_percent = :sale_tax_percent,
                    sale_tax_value = :sale_tax_value,
                    sale_tax_law_percent = :sale_tax_law_percent,
                    sale_tax_law_value = :sale_tax_law_value,
                    sale_discount_percent = :sale_discount_percent,
                    sale_discount_value = :sale_discount_value,
                    sale_cost_delivery = :sale_cost_delivery,
                    sale_type_tax_percent = :sale_type_tax_percent,
                    sale_type_tax_value = :sale_type_tax_value,
                    sale_amount_final = :sale_amount_final,
                    sale_debt = :sale_debt,
                    sale_edited_by = :sale_edited_by,
                    sale_edited_at = :sale_edited_at
                    WHERE sale_id = :sale_id
                ";

                $paramsValueUpdateSale = [
                    ':sale_id'               => $sale_id,
                    ':sale_ncf_type'         => $sale_ncf_type,
                    ':sale_ncf_number'       => $sale_ncf_number,
                    ':sale_sub_total'        => $sale_sub_total,
                    ':sale_tax_percent'      => $sale_tax_percent,
                    ':sale_tax_value'        => $sale_tax_value,
                    ':sale_tax_law_percent'  => $sale_tax_law_percent,
                    ':sale_tax_law_value'    => $sale_tax_law_value,
                    ':sale_discount_percent' => $sale_discount_percent,
                    ':sale_discount_value'   => $sale_discount_value,
                    ':sale_cost_delivery'    => $sale_cost_delivery,
                    ':sale_type_tax_percent' => $sale_type_tax_percent,
                    ':sale_type_tax_value'   => $sale_type_tax_value,
                    ':sale_amount_final'     => $sale_amount_final,
                    ':sale_debt'             => $sale_debt,
                    ':sale_edited_by'        => $session_user_id,
                    ':sale_edited_at'        => $currentDateTimeByZone
                ];

                $pdo->prepare($sqlUpdateSale)->execute($paramsValueUpdateSale);
                // $finalResponse = executeQueryPDOApiParams($sqlUpdateSale, $paramsValueUpdateSale, "UPDATED tb_sales");

            } else {
                $sqlSaveSale = "INSERT INTO tb_sales (sale_ent_id,sale_pos_id,sale_code,sale_date,sale_date_close,sale_type,sale_cus_id,sale_table_id,sale_sub_total,sale_tax_percent,sale_tax_value,sale_tax_law_percent,sale_tax_law_value,sale_discount_percent,sale_discount_value,sale_cost_delivery,sale_type_tax_percent,sale_type_tax_value,sale_amount_final,sale_debt,sale_created_by,sale_created_at)
                VALUES (:sale_ent_id,:sale_pos_id,:sale_code,:sale_date,:sale_date_close,:sale_type,:sale_cus_id,:sale_table_id,:sale_sub_total,:sale_tax_percent,:sale_tax_value,:sale_tax_law_percent,:sale_tax_law_value,:sale_discount_percent,:sale_discount_value,:sale_cost_delivery,:sale_type_tax_percent,:sale_type_tax_value,:sale_amount_final,:sale_debt,:sale_created_by,:sale_created_at
                    )
                ";

                $sale_code = createTokenNumber('tb_sales', 'sale_code', '6');

                $paramsValueSaveSale = [
                    ':sale_ent_id'            => $sale_ent_id,
                    ':sale_pos_id'            => $sale_pos_id,
                    ':sale_code'              => $sale_code,
                    ':sale_date'              => $currentDateTimeByZone,
                    ':sale_date_close'        => $sale_date_close,
                    ':sale_type'              => $sale_type,
                    ':sale_cus_id'            => $sale_cus_id,
                    ':sale_table_id'          => $sale_table_id,
                    ':sale_sub_total'         => $sale_sub_total,
                    ':sale_tax_percent'       => $sale_tax_percent,
                    ':sale_tax_value'         => $sale_tax_value,
                    ':sale_tax_law_percent'   => $sale_tax_law_percent,
                    ':sale_tax_law_value'     => $sale_tax_law_value,
                    ':sale_discount_percent'  => $sale_discount_percent,
                    ':sale_discount_value'    => $sale_discount_value,
                    ':sale_cost_delivery'     => $sale_cost_delivery,
                    ':sale_type_tax_percent'  => $sale_type_tax_percent,
                    ':sale_type_tax_value'    => $sale_type_tax_value,
                    ':sale_amount_final'      => $sale_amount_final,
                    ':sale_debt'              => $sale_debt,
                    ':sale_created_by'        => $session_user_id,
                    ':sale_created_at'        => $currentDateTimeByZone
                ];

                $pdo->prepare($sqlSaveSale)->execute($paramsValueSaveSale);

                $sale_id = $pdo->lastInsertId();
                // $finalResponse = executeQueryPDOApiParams($sqlSaveSale, $paramsValueSaveSale, "SAVED tb_sales");

                $sale_info = getSaleData($sale_id);

                if (!empty(trim((string) $sale_ncf_type))) {
                    $sale_ncf_number = generateFiscalReceipt($sale_ncf_type, $sale_ent_id, 'tb_sales', $sale_id);
                    $sql_tb_sales = "UPDATE tb_sales SET
                        sale_ncf_type = '$sale_ncf_type'
                        ,sale_ncf_number = '$sale_ncf_number'
                        WHERE sale_id={$sale_id}";
                    executeQueryPDO($sql_tb_sales);
                }

                if (floatval($sale_discount_value) > 0) {

                    $sale_discount_formated = number_format($sale_discount_value, 2);
                    $sale_sub_total_formated = number_format($sale_sub_total, 2);

                    $log_description = "{$session_user_fullname} ID:{$session_user_id}, {$posTranslate['added_new_sale']} #: {$sale_id}, {$posTranslate['and_made_a_discount_of']} {$sale_discount_percent}% {$posTranslate['which_represents_the_value_of']}: {$sale_discount_formated}, {$posTranslate['out_of_a_total_of']}: {$sale_sub_total_formated}.";

                    $saveLogs = saveLog($sale_ent_id, $sale_pos_id, $session_user_id, $log_description, $currentDateTimeByZone);
                    // sendEmail($emailSender,$emailSenderName,$emailSubject,$emailReciver,$emailReciverName,$log_description);
                }
            }

            // ---------------------------------------------------------------------
            // STEP 6 — Stock check passed and sale created:
            //          It is safe to delete old rows now in tb_sales_details  — always replaced on edit
            //          And now prepare data to insert in tb_sales_details
            // ---------------------------------------------------------------------
            if ($sale_info['status'] == 'success') {
                $sale_ent_id = $sale_info['data']['sale_ent_id'];
                $sale_pos_id = $sale_info['data']['sale_pos_id'];
                $sale_cus_id = $sale_info['data']['sale_cus_id'];
                $sale_code = $sale_info['data']['sale_code'];
                $sale_date = $sale_info['data']['sale_date'];
                $sale_det_created_by = $sale_info['data']['sale_created_by'];
                $pay_det_sale_close = $sale_info['data']['sale_date_close'];
            } else {
                $response = array("status" => "error", "message" => "{$translate['sale_not_found']} <br> {$sale_info['message']}");
                echo json_encode($response);
                exit();
            }

            $sqlSaleDetailsBase = "INSERT INTO tb_sales_details (
                    sale_det_ent_id,
                    sale_det_pos_id,
                    sale_det_sale_id,
                    sale_det_code,
                    sale_det_sale_date,
                    sale_det_ord_det_code,
                    sale_det_pro_id,
                    sale_det_pro_price,
                    sale_det_pro_qty,
                    sale_det_created_by
                ) VALUES
            ";

            $totalRows = count($_POST['sale_det_ord_det_code']);

            $saleDetailsField = [];
            $paramsValueSaleDetails = [];

            for ($row = 0; $row < $totalRows; $row++) {

                $saleDetailsField[] = "(
                    :sale_det_ent_id_$row,
                    :sale_det_pos_id_$row,
                    :sale_det_sale_id_$row,
                    :sale_det_code_$row,
                    :sale_det_sale_date_$row,
                    :sale_det_ord_det_code_$row,
                    :sale_det_pro_id_$row,
                    :sale_det_pro_price_$row,
                    :sale_det_pro_qty_$row,
                    :sale_det_created_by_$row
                )";

                $paramsValueSaleDetails[":sale_det_ent_id_$row"]        = $sale_ent_id;
                $paramsValueSaleDetails[":sale_det_pos_id_$row"]        = $sale_pos_id;
                $paramsValueSaleDetails[":sale_det_sale_id_$row"]       = $sale_id;
                $paramsValueSaleDetails[":sale_det_code_$row"]          = $sale_code;
                $paramsValueSaleDetails[":sale_det_sale_date_$row"]     = $sale_date;
                $paramsValueSaleDetails[":sale_det_ord_det_code_$row"]  = funCheckInput($_POST['sale_det_ord_det_code'][$row]);
                $paramsValueSaleDetails[":sale_det_pro_id_$row"]        = funCheckInput($_POST['sale_det_pro_id'][$row]);
                $paramsValueSaleDetails[":sale_det_pro_price_$row"]     = funCheckInput($_POST['sale_det_pro_price'][$row]);
                $paramsValueSaleDetails[":sale_det_pro_qty_$row"]       = funCheckInput($_POST['sale_det_pro_qty'][$row]);
                $paramsValueSaleDetails[":sale_det_created_by_$row"]    = $sale_det_created_by;
            }

            // Delete data from tb_sales_details
            if ($isEdit) {
                $pdo->prepare("DELETE FROM tb_sales_details WHERE sale_det_sale_id = :id")
                    ->execute([':id' => $editingSaleId]);
            }

            $sqlSaleDetails = $sqlSaleDetailsBase . implode(',', $saleDetailsField);

            // Insert data to tb_sales_details
            $pdo->prepare($sqlSaleDetails)->execute($paramsValueSaleDetails);
            // $finalResponse = executeQueryPDOApiParams($sqlSaleDetails, $paramsValueSaleDetails, "SAVED tb_sales");

            // ---------------------------------------------------------------------
            // STEP 8 — Save new order code
            // ---------------------------------------------------------------------
            if (isset($_POST["txt_ord_det_code"]) && !empty($_POST["txt_ord_det_code"])) {

                $getMaxOrderNumberBySaleId = getMaxOrderNumberBySaleId($sale_id);

                $txt_ord_det_code = funCheckInput($_POST["txt_ord_det_code"]);

                $sqlSaveOrderRequest = "INSERT INTO tb_orders_request (ord_det_ent_id,ord_det_pos_id,ord_det_number,ord_det_code,ord_det_sale_id,ord_det_sale_code,ord_det_sale_type,ord_sale_table_id,ord_det_sale_date,ord_det_cus_id,ord_det_created_by
                    ) VALUES (:ord_det_ent_id,:ord_det_pos_id,:ord_det_number,:ord_det_code,:ord_det_sale_id,:ord_det_sale_code,:ord_det_sale_type,:ord_sale_table_id,:ord_det_sale_date,:ord_det_cus_id,:ord_det_created_by
                    )
                ";

                $paramsValueSaveOrderRequest = [
                    ':ord_det_ent_id'      => $sale_ent_id,
                    ':ord_det_pos_id'      => $sale_pos_id,
                    ':ord_det_sale_id'     => $sale_id,
                    ':ord_det_number'      => $getMaxOrderNumberBySaleId,
                    ':ord_det_sale_type'   => $sale_type,
                    ':ord_det_code'        => $txt_ord_det_code,
                    ':ord_det_sale_code'   => $sale_code,
                    ':ord_sale_table_id'   => $sale_table_id,
                    ':ord_det_sale_date'   => $sale_date,
                    ':ord_det_cus_id'      => $sale_cus_id,
                    ':ord_det_created_by'  => $session_user_id,
                ];

                $pdo->prepare($sqlSaveOrderRequest)->execute($paramsValueSaveOrderRequest);
                // $finalResponse = executeQueryPDOApiParams($sqlSaveOrderRequest, $paramsValueSaveOrderRequest, "SAVED tb_orders_request");
            }

            // ---------------------------------------------------------------------------------------------
            // STEP 9 — Prepare data to insert in tb_payments_details (only if payments were provided)
            // ---------------------------------------------------------------------------------------------
            if ($hasPayments) {

                $sqlPaymentDetailsBase = "INSERT INTO tb_payments_details (pay_det_ent_id,pay_det_pos_id,pay_det_sale_id,pay_det_cus_id,sale_det_sale_code,pay_det_sale_type,pay_det_sale_date,pay_det_date,pay_det_sale_close,pay_det_payment_type,pay_det_previous_debt,pay_det_value,pay_det_income,pay_det_change,pay_det_cashback,pay_det_cashback_status,pay_det_note,pay_det_created_by) VALUES ";

                $totalRows = count($_POST['pay_det_date']);

                $paymentDetailsField = [];
                $paramsValuePaymentDetails = [];

                for ($row = 0; $row < $totalRows; $row++) {

                    $paymentDetailsField[] = "(
                        :pay_det_ent_id_$row,
                        :pay_det_pos_id_$row,
                        :pay_det_sale_id_$row,
                        :pay_det_cus_id_$row,
                        :sale_det_sale_code_$row,
                        :pay_det_sale_type_$row,
                        :pay_det_sale_date_$row,
                        :pay_det_date_$row,
                        :pay_det_sale_close_$row,
                        :pay_det_payment_type_$row,
                        :pay_det_previous_debt_$row,
                        :pay_det_value_$row,
                        :pay_det_income_$row,
                        :pay_det_change_$row,
                        :pay_det_cashback_$row,
                        :pay_det_cashback_status_$row,
                        :pay_det_note_$row,
                        :pay_det_created_by_$row
                    )";

                    $posInfo = getBranchData($sale_pos_id);

                    $pay_det_cashback_status = funCheckInput($_POST["pay_det_cashback_status"][$row]);
                    if ($_POST["pay_det_payment_type"][$row] != "CASHBACK" && intval($posInfo['pos_cash_back_availability_days']) <= 0) $pay_det_cashback_status = "Available";

                    $paramsValuePaymentDetails[":pay_det_ent_id_$row"]          = $sale_ent_id;
                    $paramsValuePaymentDetails[":pay_det_pos_id_$row"]          = $sale_pos_id;
                    $paramsValuePaymentDetails[":pay_det_sale_id_$row"]         = $sale_id;
                    $paramsValuePaymentDetails[":pay_det_cus_id_$row"]          = $sale_cus_id;
                    $paramsValuePaymentDetails[":sale_det_sale_code_$row"]      = $sale_code;
                    $paramsValuePaymentDetails[":pay_det_sale_type_$row"]       = $sale_type;
                    $paramsValuePaymentDetails[":pay_det_sale_date_$row"]       = $sale_date;
                    $paramsValuePaymentDetails[":pay_det_date_$row"]            = funCheckInput($_POST["pay_det_date"][$row]);
                    $paramsValuePaymentDetails[":pay_det_sale_close_$row"]      = $pay_det_sale_close;
                    $paramsValuePaymentDetails[":pay_det_payment_type_$row"]    = funCheckInput($_POST["pay_det_payment_type"][$row]);
                    $paramsValuePaymentDetails[":pay_det_previous_debt_$row"]   = funCheckInput($_POST["pay_det_previous_debt"][$row]);
                    $paramsValuePaymentDetails[":pay_det_value_$row"]           = funCheckInput($_POST["pay_det_value"][$row]);
                    $paramsValuePaymentDetails[":pay_det_income_$row"]          = funCheckInput($_POST["pay_det_income"][$row]);
                    $paramsValuePaymentDetails[":pay_det_change_$row"]          = funCheckInput($_POST["pay_det_change"][$row]);
                    $paramsValuePaymentDetails[":pay_det_cashback_$row"]        = funCheckInput($_POST["pay_det_cashback"][$row]);
                    $paramsValuePaymentDetails[":pay_det_cashback_status_$row"] = $pay_det_cashback_status;
                    $paramsValuePaymentDetails[":pay_det_note_$row"]            = funCheckInput($_POST["pay_det_note"][$row]);
                    $paramsValuePaymentDetails[":pay_det_created_by_$row"]      = funCheckInput($_POST["pay_det_created_by"][$row]);
                }

                if ($isEdit) {
                    $pdo->prepare("DELETE FROM tb_payments_details WHERE pay_det_sale_id = :id")
                        ->execute([':id' => $editingSaleId]);
                }

                $sqlPaymentDetails = $sqlPaymentDetailsBase . implode(',', $paymentDetailsField);

                // Insert data to tb_payments_details
                $pdo->prepare($sqlPaymentDetails)->execute($paramsValuePaymentDetails);
                // $finalResponse = executeQueryPDOApiParams($sqlPaymentDetails, $paramsValuePaymentDetails, "SAVED tb_payments_details");
            }

            // ---------------------------------------------------------------------
            // STEP 10 — Commit — lock released, next waiter may now proceed
            // ---------------------------------------------------------------------
            $pdo->commit();

            $response = array("status" => "success", "message" => "{$translate['sale']} #{$sale_id} <br> {$translate['saved_successfully']}", "printMessage" => "{$translate['do_you_want_to_print_an_invoice']}", "sale_id" => $sale_id, "sale_code" => $sale_code, "sale_cus_id" => $sale_cus_id, "sale_ent_id" => $sale_ent_id, "sale_pos_id" => $sale_pos_id);

            if ($isEdit) $response = array("status" => "success", "message" => "{$translate['sale']} #{$sale_id} <br> {$translate['updated_successfully']}", "printMessage" => "{$translate['do_you_want_to_print_an_invoice']}", "sale_id" => $sale_id, "sale_code" => $sale_code, "sale_cus_id" => $sale_cus_id, "sale_ent_id" => $sale_ent_id, "sale_pos_id" => $sale_pos_id);
        } catch (\RuntimeException | \InvalidArgumentException $e) {
            if ($pdo->inTransaction()) $pdo->rollBack();
            $response = array("status" => "error", "message" => "Database error. {$translate['execution_failed_please_contact_the_system_vendor_for_technical_assistance']} <br>" . $e->getMessage(), "redirect" => true);
        } catch (\PDOException $e) {
            if ($pdo->inTransaction()) $pdo->rollBack();
            $response = array("status" => "error", "message" => "Database error. {$translate['execution_failed_please_contact_the_system_vendor_for_technical_assistance']} <br>" . $e->getMessage(), "redirect" => true);
        }

        echo json_encode($response);

        break;


    case 'ajaxGetSaleDataById':

        $sale_id = isset($_GET["sale_id"]) ? funCheckInput($_GET["sale_id"]) : "";
        $answer = getSaleData($sale_id);
        //Codificar el resultado utilizando json
        echo json_encode($answer);

        break;


    case 'ajax_showSaleDetails':

        $sale_id = isset($_GET["sale_id"]) ? funCheckInput($_GET["sale_id"]) : "";

        $sql = "SELECT
        pro_id
        ,pro_cat_id
        ,pro_description
        ,pro_type
        ,pro_image
        ,pro_discount
        ,sale_det_pro_price
        ,sale_det_pro_qty
        ,sale_det_ord_det_code

		,IF(pro_type = 'Service','FULL',(SELECT IFNULL(sum(pur_det_pro_qty),0) FROM tb_purchases_details WHERE pur_det_pro_id=pro_id AND pur_det_status = 'Enabled')-(SELECT IFNULL(sum(sale_det_pro_qty),0) FROM tb_sales_details WHERE sale_det_pro_id=pro_id AND sale_det_status = 'Enabled')+(SELECT IFNULL(SUM(adjust_quantity),0) FROM tb_stock_adjustments WHERE adjust_product_id = pro_id AND adjust_type = 'Increase' AND adjust_status = 'Enabled')-(SELECT IFNULL(SUM(adjust_quantity),0) FROM tb_stock_adjustments WHERE adjust_product_id = pro_id AND adjust_type = 'Decrease' AND adjust_status = 'Enabled')) AS current_stock

        FROM tb_sales_details
        LEFT JOIN tb_products ON pro_id = sale_det_pro_id
        LEFT JOIN tb_sales ON sale_id = sale_det_sale_id
        WHERE sale_det_sale_id = {$sale_id};";
        // exit($sql);
        $answerSalesDetails = executePDOFetchAllAPI($sql);

        $sqlIfPaymentExist = "SELECT
        COUNT(pay_det_id) AS paymentQty
        FROM tb_payments_details
        WHERE pay_det_sale_id={$sale_id}";
        $resPaymentQty = executePDOFetchOne($sqlIfPaymentExist);

        if ($answerSalesDetails['status'] == "success") {
            $response = array("status" => "success", "data" => json_encode($answerSalesDetails['data']), "hasPayment" => $resPaymentQty["paymentQty"]);
        } else if ($answerSalesDetails['status'] == "warning") {
            $response = array("status" => "warning", "message" => $answerSalesDetails['message']);
        } else {
            $response = array("status" => "error", "message" => $answerSalesDetails['message']);
        }

        echo json_encode($response);

        break;


    case 'ajax_newOrderCode':

        echo $order_code = createToken('tb_orders_request', 'ord_det_code', '10');

        break;


    case 'ajaxCurrentDateTime':

        echo $currentDateTimeByZone;

        break;


    case 'ajaxShowPendingOrderByUser':

        $wherePosAndUserId = "AND sale_pos_id = {$session_user_pos_id} AND sale_created_by = {$session_user_id}";
        $whereDate = "AND DATE_FORMAT(ord_created_at, '%Y-%m-%d %H:%i:%s') BETWEEN '{$currentDayStart}' AND '{$currentDayEnd}'";

        $sql = "SELECT
            sale_id
            ,sale_cus_id
            ,(select cus_full_name FROM tb_customers WHERE cus_id = sale_cus_id) AS client_full_name
            ,(select table_description FROM tb_tables WHERE table_id = sale_table_id) AS table_description

            ,IFNULL((SELECT IFNULL(SUM(sale_det_pro_price*sale_det_pro_qty),0) FROM tb_sales_details WHERE sale_det_sale_id = sale_id)+(IFNULL(sale_cost_delivery,0)+IFNULL(sale_tax_value,0)+IFNULL(sale_tax_law_value,0)+(IFNULL(sale_type_tax_value,0)))-IFNULL(sale_discount_value,0),0)-(SELECT IFNULL(SUM(pay_det_value),0) FROM tb_payments_details WHERE pay_det_sale_id = sale_id) AS sale_debts

            ,sale_status
            FROM tb_sales
            LEFT JOIN tb_orders_request ON ord_det_sale_id = sale_id
            WHERE IFNULL((SELECT IFNULL(SUM(sale_det_pro_price*sale_det_pro_qty),0) FROM tb_sales_details WHERE sale_det_sale_id = sale_id)+(IFNULL(sale_cost_delivery,0)+IFNULL(sale_tax_value,0)+IFNULL(sale_tax_law_value,0)+(IFNULL(sale_type_tax_value,0)))-IFNULL(sale_discount_value,0),0)-(SELECT IFNULL(SUM(pay_det_value),0) FROM tb_payments_details WHERE pay_det_sale_id = sale_id) > 0 AND sale_status = 'Enabled' AND sale_debt_status = 'On' $wherePosAndUserId $whereDate
            GROUP BY sale_id ORDER BY sale_id DESC;";

        // exit($sql);
        $answer = executePDOFetchAll($sql, $translate['error_display_sale_details']);

        //Vamos a declarar un array
        $data = array();

        foreach ($answer as $key => $value) {
            /*=============================================
                CHECK SALE DEBT COLOR
                =============================================*/
            if ($value['sale_status'] == "Disabled" && $value['sale_debts'] > 0) {
                $saleDebts = number_format($value['sale_debts'], 2, '.', ',');
            } else if ($value['sale_status'] == "Disabled" && $value['sale_debts'] <= 0) {
                $saleDebts = "0.00";
            } else if ($value['sale_status']  == "Enabled" && $value['sale_debts'] > 0) {
                $saleDebts = number_format($value['sale_debts'], 2, '.', ',');
            } else {
                $saleDebts = "0.00";
            }

            $view = '<li><a href="sell-det-' . $value['sale_id'] . '" role="button" class="dropdown-item" href="javascript:void(0)"> <i class="fas fa-folder-open text-warning-200"></i> Abrir</a></li>';

            $client_full_name = $value['client_full_name'];
            if (empty(trim((string) $client_full_name))) $client_full_name = $empFullName;

            $data[] = array(

                "0" => "<a href='sell-det-{$value['sale_id']}' class='text-yellow'>{$value['sale_id']}</a>",
                "1" => "<a href='sell-det-{$value['sale_id']}' class='text-yellow' style='text-decoration: none;'>{$value['table_description']}</a>",
                "2" => "<a href='sell-det-{$value['sale_id']}' class='text-yellow'>{$client_full_name}</a>",
                "3" => "<a href='sell-det-{$value['sale_id']}' class='text-danger' style='text-decoration: none;'>{$saleDebts}</a>",
            );
        }
        $results = array(
            "sEcho" => 1, //Información para el datatables
            "iTotalRecords" => count($data), //enviamos el total registros al datatable
            "iTotalDisplayRecords" => count($data), //enviamos el total registros a visualizar
            "aaData" => $data
        );
        echo json_encode($results);

        break;


    case 'ajaxSalesList':

        $ent_id = isset($_GET["ent_id"]) ? funCheckInput($_GET["ent_id"]) : "";
        $pos_id = isset($_GET["pos_id"]) ? funCheckInput($_GET["pos_id"]) : "";
        $fromDate = isset($_GET["fromDate"]) ? funCheckInput($_GET["fromDate"]) : "";
        $toDate = isset($_GET["toDate"]) ? funCheckInput($_GET["toDate"]) : "";
        $status = isset($_GET["status"]) ? funCheckInput($_GET["status"]) : "";
        $empId = isset($_GET["empId"]) ? funCheckInput($_GET["empId"]) : "";

        if (empty(intval($pos_id))) exit();

        $wherePos = "AND sale_pos_id = {$pos_id}";

        if (!empty(trim((string) $pos_id))) {
            $posInfo = getBranchData($pos_id);
        }

        $DayStartTime = $posInfo["pos_day_start_time"] ?? "07:00:00";
        $DayEndTime = $posInfo["pos_day_end_time"] ?? "06:59:59";

        $fromDate = date("{$fromDate} {$DayStartTime}");
        $toDate = date("Y-m-d", strtotime("{$toDate} +1 day")) . " {$DayEndTime}";

        $whereDate = "AND sale_date BETWEEN '{$currentDayStart}' AND '{$currentDayEnd}'";
        if (!empty(trim((string) $fromDate)) && !empty(trim((string) $toDate))) {
            $whereDate = "AND sale_date BETWEEN '{$fromDate}' AND '{$toDate}'";
        }

        $whereCreatedBy = "";
        if (!empty(intval($empId))) $whereCreatedBy = " AND sale_created_by = '{$empId}'";

        $whereStatus = "AND IFNULL((SELECT IFNULL(SUM(sale_det_pro_price*sale_det_pro_qty),0) FROM tb_sales_details WHERE sale_det_sale_id = sale_id)+(IFNULL(sale_cost_delivery,0)+IFNULL(sale_tax_value,0)+IFNULL(sale_tax_law_value,0)+(IFNULL(sale_type_tax_value,0)))-IFNULL(sale_discount_value,0),0)-(SELECT IFNULL(SUM(pay_det_value),0) FROM tb_payments_details WHERE pay_det_sale_id = sale_id) > 0 AND sale_status = 'Enabled' AND sale_debt_status = 'On'";

        if ($status == "Completed") $whereStatus = "AND IFNULL((SELECT IFNULL(SUM(sale_det_pro_price*sale_det_pro_qty),0) FROM tb_sales_details WHERE sale_det_sale_id = sale_id)+(IFNULL(sale_cost_delivery,0)+IFNULL(sale_tax_value,0)+IFNULL(sale_tax_law_value,0)+(IFNULL(sale_type_tax_value,0)))-IFNULL(sale_discount_value,0),0)-(SELECT IFNULL(SUM(pay_det_value),0) FROM tb_payments_details WHERE pay_det_sale_id = sale_id) <= 0 AND sale_status = 'Enabled' AND sale_debt_status = 'On'";

        // $whereStatus = "AND sale_debt > 0 AND sale_status = 'Enabled' AND sale_debt_status = 'On'";
        // if ($status == "Completed") $whereStatus = "AND sale_debt <= 0 AND sale_status = 'Enabled' AND sale_debt_status = 'On'";
        if ($status == "Disabled") $whereStatus = " AND sale_status = 'Disabled'";

        $sql = "SELECT
            sale_id
            ,sale_cus_id
            ,sale_ent_id
            ,sale_pos_id
            ,sale_code
            ,(select table_description FROM tb_tables WHERE table_id = sale_table_id) AS table_description
            ,DATE_FORMAT(sale_date, '%d-%b-%Y %H:%i:%s') AS sale_date
            ,(SELECT pos_name FROM tb_pos WHERE sale_pos_id = pos_id) AS pos_name
            ,sale_type
            ,(SELECT cus_full_name FROM tb_customers WHERE cus_id = sale_cus_id) AS client_full_name
            ,(SELECT CONCAT(user_firstname,' ',user_lastname) FROM tb_users WHERE user_id = sale_created_by) AS waiter_full_name
            ,IFNULL((SELECT CONCAT(user_firstname,' ',user_lastname) FROM tb_users WHERE user_id = (SELECT pay_det_created_by FROM tb_payments_details WHERE pay_det_sale_id = sale_id ORDER BY pay_det_id DESC LIMIT 1)), NULL) AS cashier_full_name
            ,IFNULL((SELECT IFNULL(SUM(sale_det_pro_price*sale_det_pro_qty),0) FROM tb_sales_details WHERE sale_det_sale_id = sale_id)+(IFNULL(sale_cost_delivery,0)+IFNULL(sale_tax_value,0)+IFNULL(sale_tax_law_value,0)+(IFNULL(sale_type_tax_value,0)))-IFNULL(sale_discount_value,0),0) AS final_total
            ,IFNULL((SELECT IFNULL(SUM(sale_det_pro_price*sale_det_pro_qty),0) FROM tb_sales_details WHERE sale_det_sale_id = sale_id)+(IFNULL(sale_cost_delivery,0)+IFNULL(sale_tax_value,0)+IFNULL(sale_tax_law_value,0)+(IFNULL(sale_type_tax_value,0)))-IFNULL(sale_discount_value,0),0)-(SELECT IFNULL(SUM(pay_det_value),0) FROM tb_payments_details WHERE pay_det_sale_id = sale_id) AS sale_debts
            ,(SELECT IFNULL(SUM(pay_det_value),0) FROM tb_payments_details WHERE pay_det_sale_id = sale_id) AS total_paid
            ,sale_status
            FROM tb_sales WHERE sale_ent_id = {$ent_id} $wherePos $whereDate $whereCreatedBy $whereStatus
            GROUP BY sale_id ORDER BY sale_id DESC;";

        // exit($sql);

        $result = executePDOFetchAllAPI($sql);
        echo json_encode($result);

        break;


    case 'ajaxResetDebt':

        $transaction_id = isset($_POST["transaction_id"]) ? funCheckInput($_POST["transaction_id"]) : "";
        $transaction_type = isset($_POST["transaction_type"]) ? funCheckInput($_POST["transaction_type"]) : "";
        $transaction_reason = isset($_POST["transaction_reason"]) ? funCheckInput($_POST["transaction_reason"]) : "";

        $whereToReset = "";
        $log_description = "";
        $totalDebt = 0;

        if (!empty($transaction_id)) {

            if ($transaction_type == "sale_debt_reset") {

                $whereToReset = "WHERE sale_id = {$transaction_id}";

                $sql = "SELECT 
                sale_id
                ,sale_ent_id
                ,sale_pos_id
                ,sale_cus_id
                ,(SELECT cus_full_name FROM tb_customers WHERE cus_id = sale_cus_id) AS cus_full_name
                ,(SELECT pos_name FROM tb_pos WHERE pos_id = sale_pos_id) AS pos_name
                ,(SELECT pos_currency FROM tb_pos WHERE pos_id = sale_pos_id) AS pos_currency
                ,IFNULL(SUM(sale_debt),0) AS totalDebt
                FROM tb_sales $whereToReset;";
                $saleInfo = executePDOFetchOne($sql, "error");
                $totalDebt = $saleInfo['totalDebt'];

                if ($totalDebt <= 0) {
                    $response = array('title' => "{$translate['this_sale_has_no_debt']}", 'message' => "{$translate['there_are_no_debts_to_cancel']}", 'type' => 'warning');
                    echo json_encode($response);
                    exit();
                }

                $log_description = "{$session_user_id}-{$session_user_fullname}, {$posTranslate['canceled_the_debt_of_the_sale']} (ID: {$saleInfo['sale_id']}) {$posTranslate['of_the_pos']}: {$saleInfo['sale_pos_id']}-{$saleInfo['pos_name']}, {$posTranslate['customer']}: {$saleInfo['sale_cus_id']}-{$saleInfo['cus_full_name']}, {$posTranslate['for_a_total_of']}: {$saleInfo['pos_currency']} " . number_format($totalDebt, 2) . " {$posTranslate['for_the_reason_of']}: {$transaction_reason}.";

                $response = array('title' => "{$translate['the_debt_of_this_sale_has_been_successfully_canceled']}", 'message' => "{$translate['this_action_has_been_registered_in_the_audit_system_for_control_purposes']}", 'type' => 'success');
            }

            if ($transaction_type == "customer_debt_reset") {

                $whereToReset = "WHERE sale_status = 'Enabled' AND sale_debt > 0 AND sale_debt_status = 'On' AND sale_cus_id = '{$transaction_id}'";

                $sql = "SELECT 
                sale_ent_id
                ,sale_pos_id
                ,sale_cus_id
                ,(SELECT pos_currency FROM tb_pos WHERE pos_id = sale_pos_id) AS pos_currency
                ,(SELECT cus_full_name FROM tb_customers WHERE cus_id = sale_cus_id) AS cus_full_name
                ,IFNULL(SUM(sale_debt),0) AS totalDebt
                FROM tb_sales $whereToReset;";

                $saleInfo = executePDOFetchOne($sql, "error");
                $totalDebt = $saleInfo['totalDebt'];

                if ($totalDebt <= 0) {
                    $response = array('title' => "{$translate['this_customer_has_no_debt']}", 'message' => "{$translate['there_are_no_debts_to_cancel']}", 'type' => 'warning');
                    echo json_encode($response);
                    exit();
                }

                $log_description = "{$session_user_id}-{$session_user_fullname}, {$posTranslate['canceled_all_debts_of_the_customer']}: {$saleInfo['sale_cus_id']}-{$saleInfo['cus_full_name']}, {$posTranslate['for_a_total_of']}: {$saleInfo['pos_currency']} " . number_format($totalDebt, 2) . " {$posTranslate['for_the_reason_of']}: {$transaction_reason}.";

                $response = array('title' => "{$translate['all_debt_of_this_customer_has_been_successfully_canceled']}", 'message' => "{$translate['this_action_has_been_registered_in_the_audit_system_for_control_purposes']}", 'type' => 'success');
            }

            if ($transaction_type == "pos_debt_reset") {

                // Primera prueba de cancelacion de deuda total de un pos

                $whereToReset = "WHERE sale_status = 'Enabled' AND sale_debt > 0 AND sale_debt_status = 'On' AND sale_pos_id = '{$transaction_id}'";

                $sql = "SELECT 
                sale_ent_id
                ,sale_pos_id
                ,(SELECT pos_name FROM tb_pos WHERE pos_id = sale_pos_id) AS pos_name
                ,(SELECT pos_currency FROM tb_pos WHERE pos_id = sale_pos_id) AS pos_currency
                ,IFNULL(SUM(sale_debt),0) AS totalDebt
                FROM tb_sales $whereToReset;";

                $saleInfo = executePDOFetchOne($sql, "error");
                $totalDebt = $saleInfo['totalDebt'];

                if ($totalDebt <= 0) {
                    $response = array('title' => "{$translate['this_pos_has_no_debt']}", 'message' => "{$translate['there_are_no_debts_to_cancel']}", 'type' => 'warning');
                    echo json_encode($response);
                    exit();
                }

                $log_description = "{$session_user_fullname} ID:{$session_user_id}, {$posTranslate['canceled_all_debts_of_the_pos']}: {$saleInfo['sale_pos_id']}-{$saleInfo['pos_name']}, {$posTranslate['for_a_total_of']}: {$saleInfo['pos_currency']} " . number_format($totalDebt, 2) . " {$posTranslate['for_the_reason_of']}: {$transaction_reason}.";

                $response = array('title' => "{$translate['all_debt_from_this_pos_has_been_successfully_canceled']}", 'message' => "{$translate['this_action_has_been_registered_in_the_audit_system_for_control_purposes']}", 'type' => 'success');
            }

            $sqlUpdate = "UPDATE tb_sales SET
            sale_debt_status = 'Off'
            ,sale_debt_reset_reason = '{$transaction_reason}'
            ,sale_debt_reset_by = '$session_user_id'
            ,sale_debt_reset_at = '$currentDateTimeByZone'
        	$whereToReset ;";

            $result = executeQueryPDO($sqlUpdate, "success", "error");

            if ($result['status'] == "success") {

                fnGenerateNewCancelPassword($session_user_ent_id);

                if ($session_user_profile == "ROOT") {
                    $saveLogs = saveLog($session_user_ent_id, $session_user_pos_id, $session_user_id, $log_description, $currentDateTimeByZone, $currentDateTimeByZone);
                } else {
                    $saveLogs = saveLog($saleInfo['sale_ent_id'], $saleInfo['sale_pos_id'], $session_user_id, $log_description, $currentDateTimeByZone, $currentDateTimeByZone);
                }
                // sendEmail($emailSender,$emailSenderName,$emailSubject,$emailReciver,$emailReciverName,$log_description);
            } else {
                $response = array('title' => $translate['execution_failed'], 'message' => $translate['error_the_debt_could_not_be_reset'], 'type' => 'error');
            }
            echo json_encode($response);
        }

        break;


    case 'ajaxCancelData':

        $transaction_id = isset($_POST["transaction_id"]) ? funCheckInput($_POST["transaction_id"]) : "";
        $transaction_reason = isset($_POST["transaction_reason"]) ? funCheckInput($_POST["transaction_reason"]) : "";

        // ── 1. Validate and cast the incoming ID immediately ─────────────────────────
        $transactionId     = (int) $transaction_id;
        $transactionReason = trim($transaction_reason);

        if ($transactionId <= 0) {
            echo json_encode(['message' => $translate['invalid_sale_id'], 'status' => 'error']);
            exit();
        }

        // ── 2. Fetch sale info with a safe prepared statement ────────────────────────
        $sqlSaleInfo = "
            SELECT
                sale_id,
                sale_ent_id,
                sale_pos_id,
                ( SELECT pos_name FROM tb_pos WHERE pos_id = sale_pos_id ) AS pos_name,
                sale_code,
                sale_status,
                IFNULL(
                    ( SELECT IFNULL(SUM(sale_det_pro_price * sale_det_pro_qty), 0)
                    FROM tb_sales_details
                    WHERE sale_det_sale_id = sale_id )
                    + IFNULL(sale_cost_delivery,  0)
                    + IFNULL(sale_tax_value,       0)
                    + IFNULL(sale_tax_law_value,   0)
                    + IFNULL(sale_type_tax_value,  0)
                    - IFNULL(sale_discount_value,  0)
                , 0) AS final_total
            FROM tb_sales
            WHERE sale_id = :sale_id
        ";

        $saleInfo = executePDOFetchOneAPI($sqlSaleInfo, [':sale_id' => $transactionId]);

        // ── 3. Guard: sale must exist ─────────────────────────────────────────────────
        if ($saleInfo['status'] !== 'success' || empty($saleInfo['data'])) {
            echo json_encode(['message' => $translate['sale_not_found'], 'status' => 'error']);
            exit();
        }

        // ── 4. Guard: already cancelled ──────────────────────────────────────────────
        if ($saleInfo['data']['sale_status'] === 'Disabled') {
            echo json_encode([
                'message' => $translate['this_sale_has_already_been_canceled'],
                'status'  => 'warning'
            ]);
            exit();
        }

        // ── 5. Cancel the sale — all five UPDATEs in a single transaction ─────────────
        try {
            $pdo->beginTransaction();

            $pdo->prepare("
            UPDATE tb_sales SET
                sale_status      = 'Disabled',
                sale_cancel_reason = :reason,
                sale_canceled_by = :canceled_by,
                sale_canceled_at = :canceled_at
            WHERE sale_id = :sale_id
            ")->execute([
                ':reason'      => $transactionReason,
                ':canceled_by' => $session_user_id,
                ':canceled_at' => $currentDateTimeByZone,
                ':sale_id'     => $transactionId,
            ]);

            $pdo->prepare("
            UPDATE tb_sales_details
            SET sale_det_status = 'Disabled'
            WHERE sale_det_sale_id = :sale_id
            ")->execute([':sale_id' => $transactionId]);

            $pdo->prepare("
            UPDATE tb_orders_request
            SET ord_det_status = 'Disabled'
            WHERE ord_det_sale_id = :sale_id
            ")->execute([':sale_id' => $transactionId]);

            $pdo->prepare("
            UPDATE tb_payments_details
            SET pay_det_status = 'Disabled'
            WHERE pay_det_sale_id = :sale_id
            ")->execute([':sale_id' => $transactionId]);

            $pdo->prepare("
            UPDATE tb_fiscal_receipt_transactions
            SET fiscal_status = 'Disabled'
            WHERE fiscal_from = 'tb_sales'
            AND fiscal_from_id = :sale_id
            ")->execute([':sale_id' => $transactionId]);

            $pdo->commit();
        } catch (\PDOException $e) {
            if ($pdo->inTransaction()) $pdo->rollBack();
            echo json_encode([
                'title'   => $translate['execution_failed'],
                'message' => $translate['error_the_sale_could_not_be_canceled'],
                'status'  => 'error'
            ]);
            exit();
        }

        // ── 6. Post-cancel actions: new cancel password + audit log ───────────────────
        fnGenerateNewCancelPassword($session_user_ent_id);

        $finalTotalFormatted = number_format((float) $saleInfo['data']['final_total'], 2);

        $log_description = implode(' ', [
            "{$session_user_fullname} ID:{$session_user_id},",
            $posTranslate['canceled_a_sale'],
            "(ID:{$saleInfo['data']['sale_id']})",
            $posTranslate['of_the_pos'] . ": {$saleInfo['data']['sale_pos_id']}-{$saleInfo['data']['pos_name']},",
            $posTranslate['for_a_total_of'] . ": {$finalTotalFormatted},",
            $posTranslate['for_the_reason_of'] . ": {$transactionReason}.",
        ]);

        // ROOT logs against their own session context; all others log against the sale's entity
        $logEntId  = ($session_user_profile === 'ROOT') ? $session_user_ent_id  : $saleInfo['data']['sale_ent_id'];
        $logPosId  = ($session_user_profile === 'ROOT') ? $session_user_pos_id  : $saleInfo['data']['sale_pos_id'];

        saveLog($logEntId, $logPosId, $session_user_id, $log_description, $currentDateTimeByZone);

        // ── 7. Respond ────────────────────────────────────────────────────────────────
        echo json_encode([
            'title'   => $translate['sale_successfully_canceled'],
            'message' => $translate['this_action_has_been_registered_in_the_audit_system_for_control_purposes'],
            'status'  => 'success'
        ]);

        break;
}
