<?php
class class_GlobalEvents extends GlobalEventsBase
{
	function init() {
		$this->events = array(
	'pageEvents' => array(
		'AfterSuccessfulLogin' => true,
		'OnPageLoad' => true,
		'BeforeShowMenu' => true 
	),
	'onScreenEvents' => array(
		'162607' => true,
		'162615' => true 
	),
	'dashboardEvents' => array(
		 
	),
	'buttons' => array(
		'162601' => true,
		'162603' => true,
		'162605' => true,
		'162609' => true,
		'162611' => true,
		'162613' => true,
		'162617' => true,
		'162619' => true,
		'162621' => true,
		'162623' => true,
		'162625' => true,
		'162627' => true,
		'162629' => true 
	),
	'maps' => array(
		 
	),
	'tablePermissions' => array(
		 
	),
	'recordEditable' => array(
		 
	) 
);
	}

		function AfterSuccessfulLogin( $username, $password, &$data, $pageObject ) {
		header("Location: vw_finance_dashboard_summary_list.php");
exit();
		;
		
	}

	function BeforeShowMenu( &$xt, &$templatefile, $pageObject ) {
		header("Location: vw_finance_dashboard_summary_list.php");
exit();
		;
		
	}
	
	// custom buttons 
		// custom button - reverse_manual_journal
	function buttonHandler_162601( $_params )
	{
		$result = array();

		$button = $this->prepareButtonContext( $_params );
		$ajax = $button; 
		$keys = $button->getKeys();

		// .NET naming
		$parameters = $_params;
		$params = &$_params;

		// BuildLedger Finance - Reverse Manual Journal Button
// Fixed: created_by_platform_user_id is NOT NULL, so use original journal user as fallback.

$result["txt"] = "";
$result["reload"] = 0;
$result["new_id"] = 0;

$canReverse = true;
$journalEntryId = 0;
$newJournalEntryId = 0;
$journalNo = "";

if (isset($keys["id"])) {
    $journalEntryId = (int)$keys["id"];
}

if ($journalEntryId <= 0) {
    $result["txt"] = "Journal entry ID is missing.";
    $canReverse = false;
}

// Load original journal
if ($canReverse) {

    $rsJournal = DB::Query("
        SELECT
            id,
            journal_uuid,
            company_id,
            branch_id,
            accounting_period_id,
            journal_no,
            journal_date,
            journal_type,
            currency_code,
            exchange_rate,
            description,
            total_debit,
            total_credit,
            approval_status,
            posting_status,
            created_by_platform_user_id,
            posted_by_platform_user_id
        FROM journal_entry
        WHERE id = " . $journalEntryId . "
        LIMIT 1
    ");

    $journal = $rsJournal->fetchAssoc();

    if (!$journal) {
        $result["txt"] = "Journal entry was not found.";
        $canReverse = false;
    } elseif (strtoupper($journal["posting_status"]) != "POSTED") {
        $result["txt"] = "Only posted journal entries can be reversed.";
        $canReverse = false;
    }
}

// Period lock check
if ($canReverse) {

    $accountingPeriodId = (int)$journal["accounting_period_id"];

    $rsPeriod = DB::Query("
        SELECT period_status
        FROM accounting_period
        WHERE id = " . $accountingPeriodId . "
        LIMIT 1
    ");

    $period = $rsPeriod->fetchAssoc();

    if (!$period) {
        $result["txt"] = "Accounting period was not found.";
        $canReverse = false;
    } elseif (strtoupper($period["period_status"]) == "CLOSED") {
        $result["txt"] = "This accounting period is already closed. Reopen the period first before reversing this journal.";
        $canReverse = false;
    }
}

// Check original detail lines
if ($canReverse) {

    $rsLineCount = DB::Query("
        SELECT COUNT(*) AS cnt
        FROM journal_entry_detail
        WHERE journal_entry_id = " . $journalEntryId
    );

    $lineCountData = $rsLineCount->fetchAssoc();

    if (!$lineCountData || (int)$lineCountData["cnt"] <= 0) {
        $result["txt"] = "This journal entry has no detail lines to reverse.";
        $canReverse = false;
    }
}

// Check if reversal already exists
if ($canReverse) {

    $rsExistingReverse = DB::Query("
        SELECT
            id,
            journal_no
        FROM journal_entry
        WHERE source_transaction_type = 'MANUAL_JOURNAL_REVERSAL'
          AND source_transaction_id = " . $journalEntryId . "
        ORDER BY id DESC
        LIMIT 1
    ");

    $existingReverse = $rsExistingReverse->fetchAssoc();

    if ($existingReverse) {

        $newJournalEntryId = (int)$existingReverse["id"];
        $journalNo = $existingReverse["journal_no"];

        $rsExistingLines = DB::Query("
            SELECT COUNT(*) AS cnt
            FROM journal_entry_detail
            WHERE journal_entry_id = " . $newJournalEntryId
        );

        $existingLines = $rsExistingLines->fetchAssoc();

        if ($existingLines && (int)$existingLines["cnt"] > 0) {
            $result["txt"] = "This journal entry already has a reversal journal: " . $journalNo;
            $result["new_id"] = $newJournalEntryId;
            $canReverse = false;
        }
    }
}

// Create reversal header
if ($canReverse && $newJournalEntryId <= 0) {

    $companyId = !empty($journal["company_id"]) ? (int)$journal["company_id"] : 0;
    $branchId = !empty($journal["branch_id"]) ? (int)$journal["branch_id"] : "NULL";
    $accountingPeriodId = !empty($journal["accounting_period_id"]) ? (int)$journal["accounting_period_id"] : 0;

    if ($companyId <= 0) {
        $result["txt"] = "Company ID is missing from original journal.";
        $canReverse = false;
    }

    if ($accountingPeriodId <= 0) {
        $result["txt"] = "Accounting period ID is missing from original journal.";
        $canReverse = false;
    }
}

if ($canReverse && $newJournalEntryId <= 0) {

    // Short journal number, example: RJ28-113015
    $journalNo = "RJ" . $journalEntryId . "-" . date("His");

    $journalNoSql = db_prepare_string($journalNo);
    $journalDateSql = db_prepare_string($journal["journal_date"]);
    $currencyCodeSql = db_prepare_string($journal["currency_code"]);

    $exchangeRate = !empty($journal["exchange_rate"])
        ? number_format((float)$journal["exchange_rate"], 8, ".", "")
        : "1.00000000";

    $originalJournalNo = !empty($journal["journal_no"])
        ? $journal["journal_no"]
        : "ID " . $journalEntryId;

    $description =
        "Reversal of Journal " . $originalJournalNo .
        " - " . $journal["description"];

    $descriptionSql = db_prepare_string($description);

    $originalUuidSql = !empty($journal["journal_uuid"])
        ? db_prepare_string($journal["journal_uuid"])
        : "NULL";

    $approvalStatus = !empty($journal["approval_status"])
        ? $journal["approval_status"]
        : "NOT_SUBMITTED";

    $approvalStatusSql = db_prepare_string($approvalStatus);

    $totalDebit = number_format((float)$journal["total_credit"], 4, ".", "");
    $totalCredit = number_format((float)$journal["total_debit"], 4, ".", "");

    // created_by_platform_user_id is NOT NULL.
    // Use session user if available, otherwise use original journal creator.
    $createdBy = 0;

    if (!empty($_SESSION["platform_user_id"])) {
        $createdBy = (int)$_SESSION["platform_user_id"];
    } elseif (!empty($journal["created_by_platform_user_id"])) {
        $createdBy = (int)$journal["created_by_platform_user_id"];
    }

    if ($createdBy <= 0) {
        $result["txt"] = "Cannot create reversal because created_by_platform_user_id is missing.";
        $canReverse = false;
    }

    $postedBySql = "NULL";

    if (!empty($_SESSION["platform_user_id"])) {
        $postedBySql = (string)((int)$_SESSION["platform_user_id"]);
    } elseif (!empty($journal["posted_by_platform_user_id"])) {
        $postedBySql = (string)((int)$journal["posted_by_platform_user_id"]);
    } else {
        $postedBySql = (string)$createdBy;
    }
}

if ($canReverse && $newJournalEntryId <= 0) {

    DB::Exec("
        INSERT INTO journal_entry
        (
            journal_uuid,
            company_id,
            branch_id,
            accounting_period_id,
            journal_no,
            journal_date,
            journal_type,
            source_transaction_type,
            source_transaction_id,
            source_transaction_uuid,
            currency_code,
            exchange_rate,
            description,
            total_debit,
            total_credit,
            approval_status,
            posting_status,
            created_by_platform_user_id,
            posted_by_platform_user_id,
            created_at,
            posted_at
        )
        VALUES
        (
            UUID(),
            " . $companyId . ",
            " . $branchId . ",
            " . $accountingPeriodId . ",
            " . $journalNoSql . ",
            " . $journalDateSql . ",
            'MANUAL',
            'MANUAL_JOURNAL_REVERSAL',
            " . $journalEntryId . ",
            " . $originalUuidSql . ",
            " . $currencyCodeSql . ",
            " . $exchangeRate . ",
            " . $descriptionSql . ",
            " . $totalDebit . ",
            " . $totalCredit . ",
            " . $approvalStatusSql . ",
            'POSTED',
            " . $createdBy . ",
            " . $postedBySql . ",
            NOW(),
            NOW()
        )
    ");

    // Detect inserted reversal header
    $rsNew = DB::Query("
        SELECT
            id,
            journal_no
        FROM journal_entry
        WHERE source_transaction_type = 'MANUAL_JOURNAL_REVERSAL'
          AND source_transaction_id = " . $journalEntryId . "
        ORDER BY id DESC
        LIMIT 1
    ");

    $newJournal = $rsNew->fetchAssoc();

    if (!$newJournal) {
        $result["txt"] = "Reversal header was not created. Please check required fields.";
        $canReverse = false;
    } else {
        $newJournalEntryId = (int)$newJournal["id"];
        $journalNo = $newJournal["journal_no"];
    }
}

// Create reversing detail lines
if ($canReverse && $newJournalEntryId > 0) {

    $rsLines = DB::Query("
        SELECT
            line_no,
            account_id,
            description,
            debit_amount,
            credit_amount,
            foreign_debit_amount,
            foreign_credit_amount,
            customer_id,
            department_id,
            cost_center_id,
            project_id,
            project_task_id,
            sales_agent_id,
            campaign_id
        FROM journal_entry_detail
        WHERE journal_entry_id = " . $journalEntryId . "
        ORDER BY line_no, id
    ");

    while ($line = $rsLines->fetchAssoc()) {

        $lineNo = !empty($line["line_no"]) ? (int)$line["line_no"] : 0;
        $accountId = !empty($line["account_id"]) ? (int)$line["account_id"] : "NULL";

        $lineDescription =
            "Reversal - " .
            (!empty($line["description"]) ? $line["description"] : "Journal line " . $lineNo);

        $lineDescriptionSql = db_prepare_string($lineDescription);

        $debitAmount = number_format((float)$line["credit_amount"], 4, ".", "");
        $creditAmount = number_format((float)$line["debit_amount"], 4, ".", "");

        $foreignDebitAmount = number_format((float)$line["foreign_credit_amount"], 4, ".", "");
        $foreignCreditAmount = number_format((float)$line["foreign_debit_amount"], 4, ".", "");

        $customerId = !empty($line["customer_id"]) ? (int)$line["customer_id"] : "NULL";
        $departmentId = !empty($line["department_id"]) ? (int)$line["department_id"] : "NULL";
        $costCenterId = !empty($line["cost_center_id"]) ? (int)$line["cost_center_id"] : "NULL";
        $projectId = !empty($line["project_id"]) ? (int)$line["project_id"] : "NULL";
        $projectTaskId = !empty($line["project_task_id"]) ? (int)$line["project_task_id"] : "NULL";
        $salesAgentId = !empty($line["sales_agent_id"]) ? (int)$line["sales_agent_id"] : "NULL";
        $campaignId = !empty($line["campaign_id"]) ? (int)$line["campaign_id"] : "NULL";

        DB::Exec("
            INSERT INTO journal_entry_detail
            (
                journal_detail_uuid,
                journal_entry_id,
                line_no,
                account_id,
                description,
                debit_amount,
                credit_amount,
                foreign_debit_amount,
                foreign_credit_amount,
                customer_id,
                department_id,
                cost_center_id,
                project_id,
                project_task_id,
                sales_agent_id,
                campaign_id
            )
            VALUES
            (
                UUID(),
                " . $newJournalEntryId . ",
                " . $lineNo . ",
                " . $accountId . ",
                " . $lineDescriptionSql . ",
                " . $debitAmount . ",
                " . $creditAmount . ",
                " . $foreignDebitAmount . ",
                " . $foreignCreditAmount . ",
                " . $customerId . ",
                " . $departmentId . ",
                " . $costCenterId . ",
                " . $projectId . ",
                " . $projectTaskId . ",
                " . $salesAgentId . ",
                " . $campaignId . "
            )
        ");
    }

    $result["txt"] = "Reversal journal completed successfully: " . $journalNo;
    $result["new_id"] = $newJournalEntryId;
};

		RunnerContext::pop();
		echo runner_json_encode( $result);
		$button->deleteTempFiles();
	}

		// custom button - reopen_period
	function buttonHandler_162603( $_params )
	{
		$result = array();

		$button = $this->prepareButtonContext( $_params );
		$ajax = $button; 
		$keys = $button->getKeys();

		// .NET naming
		$parameters = $_params;
		$params = &$_params;

		// BuildLedger Finance - Reopen Accounting Period Button

$result["txt"] = "";
$result["reload"] = 0;

$canReopen = true;
$periodId = 0;

if (isset($keys["id"])) {
    $periodId = (int)$keys["id"];
}

if ($periodId <= 0) {
    $result["txt"] = "Accounting period ID is missing.";
    $canReopen = false;
}

$reopenReason = "";

if (!empty($params["reopen_reason"])) {
    $reopenReason = trim($params["reopen_reason"]);
}

if ($canReopen && $reopenReason == "") {
    $result["txt"] = "Reopen reason is required.";
    $canReopen = false;
}

// Load accounting period
if ($canReopen) {

    $rsPeriod = DB::Query("
        SELECT
            id,
            period_name,
            period_status
        FROM accounting_period
        WHERE id = " . $periodId . "
        LIMIT 1
    ");

    $period = $rsPeriod->fetchAssoc();

    if (!$period) {
        $result["txt"] = "Accounting period was not found.";
        $canReopen = false;
    } elseif (strtoupper($period["period_status"]) == "OPEN") {
        $result["txt"] = "This accounting period is already open.";
        $canReopen = false;
    }
}

// Reopen period
if ($canReopen) {

    $reasonSql = db_prepare_string($reopenReason);

    DB::Exec("
        UPDATE accounting_period
        SET
            period_status = 'OPEN',
            reopened_until = NULL,
            reopen_reason = " . $reasonSql . "
        WHERE id = " . $periodId . "
    ");

    $result["txt"] = "Accounting period reopened successfully.";
    $result["reload"] = 1;
};

		RunnerContext::pop();
		echo runner_json_encode( $result);
		$button->deleteTempFiles();
	}

		// custom button - post_customer_payment
	function buttonHandler_162605( $_params )
	{
		$result = array();

		$button = $this->prepareButtonContext( $_params );
		$ajax = $button; 
		$keys = $button->getKeys();

		// .NET naming
		$parameters = $_params;
		$params = &$_params;

		$result["success"] = false;
$result["message"] = "";

$customerPaymentId = 0;

if (isset($params["keys"]["id"])) {
    $customerPaymentId = (int)$params["keys"]["id"];
} elseif (isset($params["keys"][0]["id"])) {
    $customerPaymentId = (int)$params["keys"][0]["id"];
} elseif (isset($params["keys"][0])) {
    $customerPaymentId = (int)$params["keys"][0];
}

if ($customerPaymentId <= 0) {
    $result["message"] = "Customer payment ID was not found.";
    return;
}

$rs = DB::Query("CALL sp_post_customer_payment(" . $customerPaymentId . ")");

$row = $rs->fetchAssoc();

if (!$row) {
    $result["message"] = "Posting procedure did not return a result.";
    return;
}

$result["success"] = ((int)$row["success"] == 1);
$result["message"] = $row["message"];;

		RunnerContext::pop();
		echo runner_json_encode( $result);
		$button->deleteTempFiles();
	}

		// custom button - close_accounting_period
	function buttonHandler_162609( $_params )
	{
		$result = array();

		$button = $this->prepareButtonContext( $_params );
		$ajax = $button; 
		$keys = $button->getKeys();

		// .NET naming
		$parameters = $_params;
		$params = &$_params;

		// BuildLedger Finance - Close Accounting Period Button

$result["txt"] = "";
$result["reload"] = 0;

$canClose = true;
$periodId = 0;

if (isset($keys["id"])) {
    $periodId = (int)$keys["id"];
}

if ($periodId <= 0) {
    $result["txt"] = "Accounting period ID is missing.";
    $canClose = false;
}

// Load accounting period
if ($canClose) {

    $rsPeriod = DB::Query("
        SELECT
            id,
            period_name,
            period_status
        FROM accounting_period
        WHERE id = " . $periodId . "
        LIMIT 1
    ");

    $period = $rsPeriod->fetchAssoc();

    if (!$period) {
        $result["txt"] = "Accounting period was not found.";
        $canClose = false;
    } elseif (strtoupper($period["period_status"]) == "CLOSED") {
        $result["txt"] = "This accounting period is already closed.";
        $canClose = false;
    }
}

// Check unposted transactions
$unpostedList = array();

if ($canClose) {

    $rsSales = DB::Query("
        SELECT COUNT(*) AS cnt
        FROM sales
        WHERE accounting_period_id = " . $periodId . "
          AND UPPER(COALESCE(posting_status, '')) <> 'POSTED'
          AND UPPER(COALESCE(document_status, '')) <> 'CANCELLED'
    ");

    $sales = $rsSales->fetchAssoc();

    if ($sales && (int)$sales["cnt"] > 0) {
        $unpostedList[] = "Sales Invoice: " . (int)$sales["cnt"];
    }
}

if ($canClose) {

    $rsBills = DB::Query("
        SELECT COUNT(*) AS cnt
        FROM supplier_bill
        WHERE accounting_period_id = " . $periodId . "
          AND UPPER(COALESCE(posting_status, '')) <> 'POSTED'
          AND UPPER(COALESCE(document_status, '')) <> 'CANCELLED'
    ");

    $bills = $rsBills->fetchAssoc();

    if ($bills && (int)$bills["cnt"] > 0) {
        $unpostedList[] = "Supplier Bill: " . (int)$bills["cnt"];
    }
}

if ($canClose) {

    $rsCustomerPayments = DB::Query("
        SELECT COUNT(*) AS cnt
        FROM customer_payment
        WHERE accounting_period_id = " . $periodId . "
          AND UPPER(COALESCE(posting_status, '')) <> 'POSTED'
          AND UPPER(COALESCE(document_status, '')) <> 'CANCELLED'
    ");

    $customerPayments = $rsCustomerPayments->fetchAssoc();

    if ($customerPayments && (int)$customerPayments["cnt"] > 0) {
        $unpostedList[] = "Customer Payment: " . (int)$customerPayments["cnt"];
    }
}

if ($canClose) {

    $rsSupplierPayments = DB::Query("
        SELECT COUNT(*) AS cnt
        FROM supplier_payment
        WHERE accounting_period_id = " . $periodId . "
          AND UPPER(COALESCE(posting_status, '')) <> 'POSTED'
          AND UPPER(COALESCE(document_status, '')) <> 'CANCELLED'
    ");

    $supplierPayments = $rsSupplierPayments->fetchAssoc();

    if ($supplierPayments && (int)$supplierPayments["cnt"] > 0) {
        $unpostedList[] = "Supplier Payment: " . (int)$supplierPayments["cnt"];
    }
}

if ($canClose) {

    $rsJournals = DB::Query("
        SELECT COUNT(*) AS cnt
        FROM journal_entry
        WHERE accounting_period_id = " . $periodId . "
          AND UPPER(COALESCE(posting_status, '')) <> 'POSTED'
    ");

    $journals = $rsJournals->fetchAssoc();

    if ($journals && (int)$journals["cnt"] > 0) {
        $unpostedList[] = "Manual Journal: " . (int)$journals["cnt"];
    }
}

// Block close if unposted transactions exist
if ($canClose && count($unpostedList) > 0) {

    $result["txt"] =
        "Cannot close this accounting period. Please post or cancel these transactions first:\n\n" .
        implode("\n", $unpostedList);

    $canClose = false;
}

// Close period
if ($canClose) {

    $closedBy = 0;

    if (!empty($_SESSION["platform_user_id"])) {
        $closedBy = (int)$_SESSION["platform_user_id"];
    }

    $closedBySql = $closedBy > 0
        ? (string)$closedBy
        : "NULL";

    DB::Exec("
        UPDATE accounting_period
        SET
            period_status = 'CLOSED',
            closed_at = NOW(),
            closed_by_platform_user_id = " . $closedBySql . "
        WHERE id = " . $periodId . "
    ");

    $result["txt"] = "Accounting period closed successfully.";
    $result["reload"] = 1;
};

		RunnerContext::pop();
		echo runner_json_encode( $result);
		$button->deleteTempFiles();
	}

		// custom button - reverse_supplier_payment
	function buttonHandler_162611( $_params )
	{
		$result = array();

		$button = $this->prepareButtonContext( $_params );
		$ajax = $button; 
		$keys = $button->getKeys();

		// .NET naming
		$parameters = $_params;
		$params = &$_params;

		// BuildLedger Finance - Reverse Supplier Payment
// Creates a reversing journal entry for a posted supplier payment.

$result["success"] = false;
$result["message"] = "";

/* ============================================================
   1. Resolve Supplier Payment ID
   ============================================================ */

$supplierPaymentId = 0;

if (isset($params["keys"]["id"])) {
    $supplierPaymentId = (int) $params["keys"]["id"];
} elseif (isset($params["keys"][0]["id"])) {
    $supplierPaymentId = (int) $params["keys"][0]["id"];
} elseif (isset($params["keys"][0])) {
    $supplierPaymentId = (int) $params["keys"][0];
}

if ($supplierPaymentId <= 0) {
    $result["message"] = "Supplier payment ID was not found.";
    return;
}

/* ============================================================
   2. Load Supplier Payment
   ============================================================ */

$rsPayment = DB::Query("
    SELECT
        id,
        supplier_payment_uuid,
        company_id,
        branch_id,
        accounting_period_id,
        payment_no,
        payment_date,
        supplier_id,
        currency_code,
        exchange_rate,
        reference_no,
        document_status,
        posting_status,
        created_by_platform_user_id
    FROM supplier_payment
    WHERE id = " . $supplierPaymentId . "
    LIMIT 1
");

$payment = $rsPayment->fetchAssoc();

if (!$payment) {
    $result["message"] = "Supplier payment was not found.";
    return;
}

if ($payment["posting_status"] != "POSTED") {
    $result["message"] = "Only posted supplier payments can be reversed.";
    return;
}

if ($payment["document_status"] == "CANCELLED") {
    $result["message"] = "This supplier payment is already cancelled/reversed.";
    return;
}

$companyId = (int) $payment["company_id"];
$branchId = !empty($payment["branch_id"]) ? (int) $payment["branch_id"] : "NULL";
$accountingPeriodId = (int) $payment["accounting_period_id"];
/* ============================================================
   Period Lock Check
   ============================================================ */

$rsPeriod = DB::Query("
    SELECT period_status
    FROM accounting_period
    WHERE id = " . (int)$accountingPeriodId . "
    LIMIT 1
");

$period = $rsPeriod->fetchAssoc();

if (!$period) {
    $result["message"] = "Accounting period was not found.";
    return;
}

if (strtoupper($period["period_status"]) == "CLOSED") {
    $result["message"] = "This accounting period is already closed. This action is not allowed.";
    return;
}

$paymentUuid = db_addslashes($payment["supplier_payment_uuid"]);

$paymentNo = !empty($payment["payment_no"])
    ? db_addslashes($payment["payment_no"])
    : db_addslashes($payment["reference_no"]);

$currencyCode = !empty($payment["currency_code"]) ? db_addslashes($payment["currency_code"]) : "PHP";
$exchangeRate = !empty($payment["exchange_rate"]) ? (float) $payment["exchange_rate"] : 1;

$userId = 0;

if (!empty($_SESSION["platform_user_id"])) {
    $userId = (int) $_SESSION["platform_user_id"];
} elseif (!empty($payment["created_by_platform_user_id"])) {
    $userId = (int) $payment["created_by_platform_user_id"];
}

/* ============================================================
   3. Find Original Journal Entry
   ============================================================ */

$rsOriginal = DB::Query("
    SELECT
        id,
        journal_no,
        total_debit,
        total_credit
    FROM journal_entry
    WHERE company_id = " . $companyId . "
      AND source_transaction_type = 'SUPPLIER_PAYMENT'
      AND source_transaction_id = " . $supplierPaymentId . "
      AND posting_status = 'POSTED'
    LIMIT 1
");

$original = $rsOriginal->fetchAssoc();

if (!$original) {
    $result["message"] = "Original posted journal entry was not found.";
    return;
}

$originalJournalEntryId = (int) $original["id"];
$originalJournalNo = db_addslashes($original["journal_no"]);

$totalDebit = round((float) $original["total_credit"], 4);
$totalCredit = round((float) $original["total_debit"], 4);

if ($totalDebit <= 0 || $totalCredit <= 0) {
    $result["message"] = "Original journal entry has invalid totals.";
    return;
}

if (round($totalDebit, 4) != round($totalCredit, 4)) {
    $result["message"] = "Reversal journal entry is not balanced.";
    return;
}

/* ============================================================
   4. Prevent Duplicate Reversal
   ============================================================ */

$rsExisting = DB::Query("
    SELECT id, journal_no
    FROM journal_entry
    WHERE company_id = " . $companyId . "
      AND source_transaction_type = 'SUPPLIER_PAYMENT_REVERSAL'
      AND source_transaction_id = " . $supplierPaymentId . "
      AND posting_status = 'POSTED'
    LIMIT 1
");

$existing = $rsExisting->fetchAssoc();

if ($existing) {
    DB::Exec("
        UPDATE supplier_payment
        SET document_status = 'CANCELLED'
        WHERE id = " . $supplierPaymentId . "
        LIMIT 1
    ");

    $result["success"] = true;
    $result["message"] = "Supplier payment was already reversed. Reversal Journal No: " . $existing["journal_no"];
    return;
}

/* ============================================================
   5. Helpers
   ============================================================ */

$fmt = function ($value) {
    return number_format((float)$value, 4, ".", "");
};

$nullableInt = function ($value) {
    if ($value === "" || $value === null) {
        return "NULL";
    }

    if ((int)$value <= 0) {
        return "NULL";
    }

    return (string)((int)$value);
};

/* ============================================================
   6. Create Reversal Journal Entry
   ============================================================ */

$rsUuid = DB::Query("SELECT UUID() AS uuid");
$dataUuid = $rsUuid->fetchAssoc();
$journalUuid = $dataUuid ? $dataUuid["uuid"] : "";

if ($journalUuid == "") {
    $result["message"] = "Unable to generate journal UUID.";
    return;
}

$reversalDate = date("Y-m-d");

DB::Exec("START TRANSACTION");

DB::Exec("
    INSERT INTO journal_entry
    (
        journal_uuid,
        company_id,
        branch_id,
        accounting_period_id,
        journal_no,
        journal_date,
        journal_type,
        source_transaction_type,
        source_transaction_id,
        source_transaction_uuid,
        currency_code,
        exchange_rate,
        description,
        total_debit,
        total_credit,
        approval_status,
        posting_status,
        created_by_platform_user_id,
        posted_by_platform_user_id,
        posted_at
    )
    VALUES
    (
        '" . db_addslashes($journalUuid) . "',
        " . $companyId . ",
        " . $branchId . ",
        " . $accountingPeriodId . ",
        NULL,
        '" . $reversalDate . "',
        'REVERSAL',
        'SUPPLIER_PAYMENT_REVERSAL',
        " . $supplierPaymentId . ",
        '" . $paymentUuid . "',
        '" . $currencyCode . "',
        " . $fmt($exchangeRate) . ",
        'Reversal of Supplier Payment " . $paymentNo . " / Original Journal " . $originalJournalNo . "',
        " . $fmt($totalDebit) . ",
        " . $fmt($totalCredit) . ",
        'APPROVED',
        'POSTED',
        " . $userId . ",
        " . $userId . ",
        CURRENT_TIMESTAMP(6)
    )
");

$rsJournal = DB::Query("
    SELECT id
    FROM journal_entry
    WHERE journal_uuid = '" . db_addslashes($journalUuid) . "'
    LIMIT 1
");

$journal = $rsJournal->fetchAssoc();

if (!$journal) {
    DB::Exec("ROLLBACK");
    $result["message"] = "Reversal journal entry was not created.";
    return;
}

$journalEntryId = (int) $journal["id"];
$journalNo = "JE-" . date("Ym") . "-" . str_pad($journalEntryId, 6, "0", STR_PAD_LEFT);

DB::Exec("
    UPDATE journal_entry
    SET journal_no = '" . db_addslashes($journalNo) . "'
    WHERE id = " . $journalEntryId . "
    LIMIT 1
");

/* ============================================================
   7. Reverse Original Journal Details
   ============================================================ */

$rsDetails = DB::Query("
    SELECT
        line_no,
        account_id,
        description,
        debit_amount,
        credit_amount,
        foreign_debit_amount,
        foreign_credit_amount,
        customer_id,
        department_id,
        cost_center_id,
        project_id,
        project_task_id,
        sales_agent_id,
        campaign_id
    FROM journal_entry_detail
    WHERE journal_entry_id = " . $originalJournalEntryId . "
    ORDER BY line_no, id
");

$lineNo = 1;
$detailCount = 0;

while ($detail = $rsDetails->fetchAssoc()) {

    $desc = db_addslashes($detail["description"]);

    DB::Exec("
        INSERT INTO journal_entry_detail
        (
            journal_detail_uuid,
            journal_entry_id,
            line_no,
            account_id,
            description,
            debit_amount,
            credit_amount,
            foreign_debit_amount,
            foreign_credit_amount,
            customer_id,
            department_id,
            cost_center_id,
            project_id,
            project_task_id,
            sales_agent_id,
            campaign_id
        )
        VALUES
        (
            UUID(),
            " . $journalEntryId . ",
            " . $lineNo . ",
            " . (int)$detail["account_id"] . ",
            'REVERSAL - " . $desc . "',
            " . $fmt($detail["credit_amount"]) . ",
            " . $fmt($detail["debit_amount"]) . ",
            " . $fmt($detail["foreign_credit_amount"]) . ",
            " . $fmt($detail["foreign_debit_amount"]) . ",
            " . $nullableInt($detail["customer_id"]) . ",
            " . $nullableInt($detail["department_id"]) . ",
            " . $nullableInt($detail["cost_center_id"]) . ",
            " . $nullableInt($detail["project_id"]) . ",
            " . $nullableInt($detail["project_task_id"]) . ",
            " . $nullableInt($detail["sales_agent_id"]) . ",
            " . $nullableInt($detail["campaign_id"]) . "
        )
    ");

    $lineNo++;
    $detailCount++;
}

if ($detailCount == 0) {
    DB::Exec("ROLLBACK");
    $result["message"] = "Original journal entry has no details to reverse.";
    return;
}

/* ============================================================
   8. Mark Supplier Payment as Cancelled
   ============================================================ */

DB::Exec("
    UPDATE supplier_payment
    SET document_status = 'CANCELLED'
    WHERE id = " . $supplierPaymentId . "
    LIMIT 1
");

DB::Exec("COMMIT");

$result["success"] = true;
$result["message"] = "Supplier payment reversed successfully. Reversal Journal No: " . $journalNo;;

		RunnerContext::pop();
		echo runner_json_encode( $result);
		$button->deleteTempFiles();
	}

		// custom button - post_sales_invoice
	function buttonHandler_162613( $_params )
	{
		$result = array();

		$button = $this->prepareButtonContext( $_params );
		$ajax = $button; 
		$keys = $button->getKeys();

		// .NET naming
		$parameters = $_params;
		$params = &$_params;

		$result["success"] = false;
$result["message"] = "";

$salesId = 0;

if (isset($params["keys"]["id"])) {
    $salesId = (int)$params["keys"]["id"];
} elseif (isset($params["keys"][0]["id"])) {
    $salesId = (int)$params["keys"][0]["id"];
} elseif (isset($params["keys"][0])) {
    $salesId = (int)$params["keys"][0];
}

if ($salesId <= 0) {
    $result["message"] = "Sales invoice ID was not found.";
    return;
}

$rs = DB::Query("CALL sp_post_sales_invoice(" . $salesId . ")");

$row = $rs->fetchAssoc();

if (!$row) {
    $result["message"] = "Posting procedure did not return a result.";
    return;
}

$result["success"] = ((int)$row["success"] == 1);
$result["message"] = $row["message"];;

		RunnerContext::pop();
		echo runner_json_encode( $result);
		$button->deleteTempFiles();
	}

		// custom button - reverse_supplier_bill
	function buttonHandler_162617( $_params )
	{
		$result = array();

		$button = $this->prepareButtonContext( $_params );
		$ajax = $button; 
		$keys = $button->getKeys();

		// .NET naming
		$parameters = $_params;
		$params = &$_params;

		// BuildLedger Finance - Reverse Supplier Bill
// Creates a reversing journal entry for a posted supplier bill.

$result["success"] = false;
$result["message"] = "";

/* ============================================================
   1. Resolve Supplier Bill ID
   ============================================================ */

$supplierBillId = 0;

if (isset($params["keys"]["id"])) {
    $supplierBillId = (int) $params["keys"]["id"];
} elseif (isset($params["keys"][0]["id"])) {
    $supplierBillId = (int) $params["keys"][0]["id"];
} elseif (isset($params["keys"][0])) {
    $supplierBillId = (int) $params["keys"][0];
}

if ($supplierBillId <= 0) {
    $result["message"] = "Supplier bill ID was not found.";
    return;
}

/* ============================================================
   2. Load Supplier Bill
   ============================================================ */

$rsBill = DB::Query("
    SELECT
        id,
        supplier_bill_uuid,
        company_id,
        branch_id,
        accounting_period_id,
        bill_no,
        supplier_invoice_no,
        bill_date,
        supplier_id,
        currency_code,
        exchange_rate,
        document_status,
        posting_status,
        created_by_platform_user_id
    FROM supplier_bill
    WHERE id = " . $supplierBillId . "
    LIMIT 1
");

$bill = $rsBill->fetchAssoc();

if (!$bill) {
    $result["message"] = "Supplier bill was not found.";
    return;
}

if ($bill["posting_status"] != "POSTED") {
    $result["message"] = "Only posted supplier bills can be reversed.";
    return;
}

if ($bill["document_status"] == "CANCELLED") {
    $result["message"] = "This supplier bill is already cancelled/reversed.";
    return;
}

$companyId = (int) $bill["company_id"];
$branchId = !empty($bill["branch_id"]) ? (int) $bill["branch_id"] : "NULL";
$accountingPeriodId = (int) $bill["accounting_period_id"];
/* ============================================================
   Period Lock Check
   ============================================================ */

$rsPeriod = DB::Query("
    SELECT period_status
    FROM accounting_period
    WHERE id = " . (int)$accountingPeriodId . "
    LIMIT 1
");

$period = $rsPeriod->fetchAssoc();

if (!$period) {
    $result["message"] = "Accounting period was not found.";
    return;
}

if (strtoupper($period["period_status"]) == "CLOSED") {
    $result["message"] = "This accounting period is already closed. This action is not allowed.";
    return;
}
$billUuid = db_addslashes($bill["supplier_bill_uuid"]);

$billNo = !empty($bill["bill_no"])
    ? db_addslashes($bill["bill_no"])
    : db_addslashes($bill["supplier_invoice_no"]);

$currencyCode = !empty($bill["currency_code"]) ? db_addslashes($bill["currency_code"]) : "PHP";
$exchangeRate = !empty($bill["exchange_rate"]) ? (float) $bill["exchange_rate"] : 1;

$userId = 0;

if (!empty($_SESSION["platform_user_id"])) {
    $userId = (int) $_SESSION["platform_user_id"];
} elseif (!empty($bill["created_by_platform_user_id"])) {
    $userId = (int) $bill["created_by_platform_user_id"];
}

/* ============================================================
   3. Block reversal if active posted supplier payments still exist
   ============================================================ */

$rsActivePayments = DB::Query("
    SELECT
        COALESCE(SUM(spa.applied_amount), 0) AS active_applied_amount
    FROM supplier_payment_application spa
    INNER JOIN supplier_payment sp
        ON sp.id = spa.supplier_payment_id
    WHERE spa.supplier_bill_id = " . $supplierBillId . "
      AND sp.posting_status = 'POSTED'
      AND COALESCE(sp.document_status, '') <> 'CANCELLED'
");

$activePayments = $rsActivePayments->fetchAssoc();
$activeAppliedAmount = $activePayments ? round((float)$activePayments["active_applied_amount"], 4) : 0;

if ($activeAppliedAmount > 0) {
    $result["message"] = "This supplier bill still has active posted payments. Reverse the supplier payment first.";
    return;
}

/* ============================================================
   4. Find Original Journal Entry
   ============================================================ */

$rsOriginal = DB::Query("
    SELECT
        id,
        journal_no,
        total_debit,
        total_credit
    FROM journal_entry
    WHERE company_id = " . $companyId . "
      AND source_transaction_type = 'SUPPLIER_BILL'
      AND source_transaction_id = " . $supplierBillId . "
      AND posting_status = 'POSTED'
    LIMIT 1
");

$original = $rsOriginal->fetchAssoc();

if (!$original) {
    $result["message"] = "Original posted journal entry was not found.";
    return;
}

$originalJournalEntryId = (int) $original["id"];
$originalJournalNo = db_addslashes($original["journal_no"]);

$totalDebit = round((float) $original["total_credit"], 4);
$totalCredit = round((float) $original["total_debit"], 4);

if ($totalDebit <= 0 || $totalCredit <= 0) {
    $result["message"] = "Original journal entry has invalid totals.";
    return;
}

if (round($totalDebit, 4) != round($totalCredit, 4)) {
    $result["message"] = "Reversal journal entry is not balanced.";
    return;
}

/* ============================================================
   5. Prevent Duplicate Reversal
   ============================================================ */

$rsExisting = DB::Query("
    SELECT id, journal_no
    FROM journal_entry
    WHERE company_id = " . $companyId . "
      AND source_transaction_type = 'SUPPLIER_BILL_REVERSAL'
      AND source_transaction_id = " . $supplierBillId . "
      AND posting_status = 'POSTED'
    LIMIT 1
");

$existing = $rsExisting->fetchAssoc();

if ($existing) {
    DB::Exec("
        UPDATE supplier_bill
        SET
            document_status = 'CANCELLED',
            cancelled_at = CURRENT_TIMESTAMP(6)
        WHERE id = " . $supplierBillId . "
        LIMIT 1
    ");

    $result["success"] = true;
    $result["message"] = "Supplier bill was already reversed. Reversal Journal No: " . $existing["journal_no"];
    return;
}

/* ============================================================
   6. Helpers
   ============================================================ */

$fmt = function ($value) {
    return number_format((float)$value, 4, ".", "");
};

$nullableInt = function ($value) {
    if ($value === "" || $value === null) {
        return "NULL";
    }

    if ((int)$value <= 0) {
        return "NULL";
    }

    return (string)((int)$value);
};

/* ============================================================
   7. Create Reversal Journal Entry
   ============================================================ */

$rsUuid = DB::Query("SELECT UUID() AS uuid");
$dataUuid = $rsUuid->fetchAssoc();
$journalUuid = $dataUuid ? $dataUuid["uuid"] : "";

if ($journalUuid == "") {
    $result["message"] = "Unable to generate journal UUID.";
    return;
}

$reversalDate = date("Y-m-d");

DB::Exec("START TRANSACTION");

DB::Exec("
    INSERT INTO journal_entry
    (
        journal_uuid,
        company_id,
        branch_id,
        accounting_period_id,
        journal_no,
        journal_date,
        journal_type,
        source_transaction_type,
        source_transaction_id,
        source_transaction_uuid,
        currency_code,
        exchange_rate,
        description,
        total_debit,
        total_credit,
        approval_status,
        posting_status,
        created_by_platform_user_id,
        posted_by_platform_user_id,
        posted_at
    )
    VALUES
    (
        '" . db_addslashes($journalUuid) . "',
        " . $companyId . ",
        " . $branchId . ",
        " . $accountingPeriodId . ",
        NULL,
        '" . $reversalDate . "',
        'REVERSAL',
        'SUPPLIER_BILL_REVERSAL',
        " . $supplierBillId . ",
        '" . $billUuid . "',
        '" . $currencyCode . "',
        " . $fmt($exchangeRate) . ",
        'Reversal of Supplier Bill " . $billNo . " / Original Journal " . $originalJournalNo . "',
        " . $fmt($totalDebit) . ",
        " . $fmt($totalCredit) . ",
        'APPROVED',
        'POSTED',
        " . $userId . ",
        " . $userId . ",
        CURRENT_TIMESTAMP(6)
    )
");

$rsJournal = DB::Query("
    SELECT id
    FROM journal_entry
    WHERE journal_uuid = '" . db_addslashes($journalUuid) . "'
    LIMIT 1
");

$journal = $rsJournal->fetchAssoc();

if (!$journal) {
    DB::Exec("ROLLBACK");
    $result["message"] = "Reversal journal entry was not created.";
    return;
}

$journalEntryId = (int) $journal["id"];
$journalNo = "JE-" . date("Ym") . "-" . str_pad($journalEntryId, 6, "0", STR_PAD_LEFT);

DB::Exec("
    UPDATE journal_entry
    SET journal_no = '" . db_addslashes($journalNo) . "'
    WHERE id = " . $journalEntryId . "
    LIMIT 1
");

/* ============================================================
   8. Reverse Original Journal Details
   ============================================================ */

$rsDetails = DB::Query("
    SELECT
        line_no,
        account_id,
        description,
        debit_amount,
        credit_amount,
        foreign_debit_amount,
        foreign_credit_amount,
        customer_id,
        department_id,
        cost_center_id,
        project_id,
        project_task_id,
        sales_agent_id,
        campaign_id
    FROM journal_entry_detail
    WHERE journal_entry_id = " . $originalJournalEntryId . "
    ORDER BY line_no, id
");

$lineNo = 1;
$detailCount = 0;

while ($detail = $rsDetails->fetchAssoc()) {

    $desc = db_addslashes($detail["description"]);

    DB::Exec("
        INSERT INTO journal_entry_detail
        (
            journal_detail_uuid,
            journal_entry_id,
            line_no,
            account_id,
            description,
            debit_amount,
            credit_amount,
            foreign_debit_amount,
            foreign_credit_amount,
            customer_id,
            department_id,
            cost_center_id,
            project_id,
            project_task_id,
            sales_agent_id,
            campaign_id
        )
        VALUES
        (
            UUID(),
            " . $journalEntryId . ",
            " . $lineNo . ",
            " . (int)$detail["account_id"] . ",
            'REVERSAL - " . $desc . "',
            " . $fmt($detail["credit_amount"]) . ",
            " . $fmt($detail["debit_amount"]) . ",
            " . $fmt($detail["foreign_credit_amount"]) . ",
            " . $fmt($detail["foreign_debit_amount"]) . ",
            " . $nullableInt($detail["customer_id"]) . ",
            " . $nullableInt($detail["department_id"]) . ",
            " . $nullableInt($detail["cost_center_id"]) . ",
            " . $nullableInt($detail["project_id"]) . ",
            " . $nullableInt($detail["project_task_id"]) . ",
            " . $nullableInt($detail["sales_agent_id"]) . ",
            " . $nullableInt($detail["campaign_id"]) . "
        )
    ");

    $lineNo++;
    $detailCount++;
}

if ($detailCount == 0) {
    DB::Exec("ROLLBACK");
    $result["message"] = "Original journal entry has no details to reverse.";
    return;
}

/* ============================================================
   9. Mark Supplier Bill as Cancelled
   ============================================================ */

DB::Exec("
    UPDATE supplier_bill
    SET
        document_status = 'CANCELLED',
        cancelled_at = CURRENT_TIMESTAMP(6)
    WHERE id = " . $supplierBillId . "
    LIMIT 1
");

DB::Exec("COMMIT");

$result["success"] = true;
$result["message"] = "Supplier bill reversed successfully. Reversal Journal No: " . $journalNo;;

		RunnerContext::pop();
		echo runner_json_encode( $result);
		$button->deleteTempFiles();
	}

		// custom button - post_manual_journal
	function buttonHandler_162619( $_params )
	{
		$result = array();

		$button = $this->prepareButtonContext( $_params );
		$ajax = $button; 
		$keys = $button->getKeys();

		// .NET naming
		$parameters = $_params;
		$params = &$_params;

		/* ============================================================
   BuildLedger Finance - Post Manual Journal
   PHPRunner-safe no-return version
   ============================================================ */

$result["txt"] = "";
$result["reload"] = 0;

$canPost = true;
$journalEntryId = 0;
$accountingPeriodId = 0;
$totalDebit = 0;
$totalCredit = 0;
$lineCount = 0;

if (isset($keys["id"])) {
    $journalEntryId = (int)$keys["id"];
}

if ($journalEntryId <= 0) {

    $result["txt"] = "Journal entry ID is missing.";
    $canPost = false;
}

/* ============================================================
   Load journal header
   ============================================================ */

if ($canPost) {

    $rsJournal = DB::Query("
        SELECT
            id,
            accounting_period_id,
            posting_status
        FROM journal_entry
        WHERE id = " . $journalEntryId . "
        LIMIT 1
    ");

    $journal = $rsJournal->fetchAssoc();

    if (!$journal) {

        $result["txt"] = "Journal entry was not found.";
        $canPost = false;

    } elseif (strtoupper($journal["posting_status"]) == "POSTED") {

        $result["txt"] = "This journal entry is already posted.";
        $canPost = false;

    } else {

        $accountingPeriodId = (int)$journal["accounting_period_id"];
    }
}

/* ============================================================
   Period lock check
   ============================================================ */

if ($canPost && $accountingPeriodId > 0) {

    $rsPeriod = DB::Query("
        SELECT period_status
        FROM accounting_period
        WHERE id = " . $accountingPeriodId . "
        LIMIT 1
    ");

    $period = $rsPeriod->fetchAssoc();

    if (!$period) {

        $result["txt"] = "Accounting period was not found.";
        $canPost = false;

    } elseif (strtoupper($period["period_status"]) == "CLOSED") {

        $result["txt"] = "This accounting period is already closed. Posting is not allowed.";
        $canPost = false;
    }
}

/* ============================================================
   Validate detail lines
   ============================================================ */

if ($canPost) {

    $rsLines = DB::Query("
        SELECT
            id,
            line_no,
            account_id,
            debit_amount,
            credit_amount
        FROM journal_entry_detail
        WHERE journal_entry_id = " . $journalEntryId . "
        ORDER BY line_no, id
    ");

    while ($line = $rsLines->fetchAssoc()) {

        $lineCount++;

        $lineNo = !empty($line["line_no"])
            ? (int)$line["line_no"]
            : $lineCount;

        $accountId = !empty($line["account_id"])
            ? (int)$line["account_id"]
            : 0;

        $debitAmount = !empty($line["debit_amount"])
            ? round((float)$line["debit_amount"], 4)
            : 0;

        $creditAmount = !empty($line["credit_amount"])
            ? round((float)$line["credit_amount"], 4)
            : 0;

        if ($accountId <= 0) {

            $result["txt"] = "Line " . $lineNo . " has no account selected.";
            $canPost = false;
            break;
        }

        if ($debitAmount <= 0 && $creditAmount <= 0) {

            $result["txt"] = "Line " . $lineNo . " has zero debit and zero credit.";
            $canPost = false;
            break;
        }

        if ($debitAmount > 0 && $creditAmount > 0) {

            $result["txt"] = "Line " . $lineNo . " cannot have both debit and credit amount.";
            $canPost = false;
            break;
        }

        $totalDebit += $debitAmount;
        $totalCredit += $creditAmount;
    }
}

$totalDebit = round($totalDebit, 4);
$totalCredit = round($totalCredit, 4);

if ($canPost && $lineCount < 2) {

    $result["txt"] = "Journal entry must have at least two detail lines.";
    $canPost = false;
}

if ($canPost && round($totalDebit - $totalCredit, 2) != 0) {

    $result["txt"] =
        "Journal entry is not balanced. Total Debit is " .
        number_format($totalDebit, 2) .
        " and Total Credit is " .
        number_format($totalCredit, 2) .
        ".";

    $canPost = false;
}

/* ============================================================
   Post journal entry
   ============================================================ */

if ($canPost) {

    $totalDebitSql = number_format($totalDebit, 4, ".", "");
    $totalCreditSql = number_format($totalCredit, 4, ".", "");

    $postedBy = 0;

    if (!empty($_SESSION["platform_user_id"])) {
        $postedBy = (int)$_SESSION["platform_user_id"];
    }

    DB::Exec("
        UPDATE journal_entry
        SET
            total_debit = " . $totalDebitSql . ",
            total_credit = " . $totalCreditSql . ",
            posting_status = 'POSTED',
            posted_by_platform_user_id = " . $postedBy . ",
            posted_at = NOW()
        WHERE id = " . $journalEntryId . "
    ");

    $result["txt"] =
        "Manual journal posted successfully. Total Debit: " .
        number_format($totalDebit, 2) .
        " / Total Credit: " .
        number_format($totalCredit, 2) .
        ".";

    $result["reload"] = 1;
};

		RunnerContext::pop();
		echo runner_json_encode( $result);
		$button->deleteTempFiles();
	}

		// custom button - post_supplier_bill
	function buttonHandler_162621( $_params )
	{
		$result = array();

		$button = $this->prepareButtonContext( $_params );
		$ajax = $button; 
		$keys = $button->getKeys();

		// .NET naming
		$parameters = $_params;
		$params = &$_params;

		// BuildLedger Finance - Post Supplier Bill
// Creates journal_entry and journal_entry_detail records for Supplier Bill.

$result["success"] = false;
$result["message"] = "";

/* ============================================================
   1. Resolve Supplier Bill ID from button
   ============================================================ */

$supplierBillId = 0;

if (isset($params["keys"]["id"])) {
    $supplierBillId = (int) $params["keys"]["id"];
} elseif (isset($params["keys"][0]["id"])) {
    $supplierBillId = (int) $params["keys"][0]["id"];
} elseif (isset($params["keys"][0])) {
    $supplierBillId = (int) $params["keys"][0];
}

if ($supplierBillId <= 0) {
    $result["message"] = "Supplier bill ID was not found.";
    return;
}

/* ============================================================
   2. Load Supplier Bill
   ============================================================ */

$rsBill = DB::Query("
    SELECT
        id,
        supplier_bill_uuid,
        company_id,
        branch_id,
        accounting_period_id,
        document_type,
        bill_no,
        supplier_invoice_no,
        bill_date,
        supplier_id,
        currency_code,
        exchange_rate,
        subtotal_amount,
        discount_amount,
        input_vat_amount,
        withholding_amount,
        grand_total_amount,
        approval_status,
        document_status,
        posting_status,
        created_by_platform_user_id,
        department_id,
        cost_center_id,
        project_id,
        project_task_id
    FROM supplier_bill
    WHERE id = " . $supplierBillId . "
    LIMIT 1
");

$bill = $rsBill->fetchAssoc();

if (!$bill) {
    $result["message"] = "Supplier bill was not found.";
    return;
}

if ($bill["posting_status"] == "POSTED") {
    $result["message"] = "This supplier bill is already posted.";
    return;
}

$companyId = (int) $bill["company_id"];
$branchId = !empty($bill["branch_id"]) ? (int) $bill["branch_id"] : "NULL";
$accountingPeriodId = (int) $bill["accounting_period_id"];
/* ============================================================
   Period Lock Check
   ============================================================ */

$rsPeriod = DB::Query("
    SELECT period_status
    FROM accounting_period
    WHERE id = " . (int)$accountingPeriodId . "
    LIMIT 1
");

$period = $rsPeriod->fetchAssoc();

if (!$period) {
    $result["message"] = "Accounting period was not found.";
    return;
}

if (strtoupper($period["period_status"]) == "CLOSED") {
    $result["message"] = "This accounting period is already closed. This action is not allowed.";
    return;
}
$billDate = db_addslashes($bill["bill_date"]);
$billUuid = db_addslashes($bill["supplier_bill_uuid"]);

$billNo = !empty($bill["bill_no"])
    ? db_addslashes($bill["bill_no"])
    : db_addslashes($bill["supplier_invoice_no"]);

$currencyCode = !empty($bill["currency_code"]) ? db_addslashes($bill["currency_code"]) : "PHP";
$exchangeRate = !empty($bill["exchange_rate"]) ? (float) $bill["exchange_rate"] : 1;

$grandTotal = round((float) $bill["grand_total_amount"], 4);
$headerInputVat = round((float) $bill["input_vat_amount"], 4);
$headerWithholding = round((float) $bill["withholding_amount"], 4);

if ($grandTotal <= 0) {
    $result["message"] = "Grand total amount must be greater than zero.";
    return;
}

$userId = 0;

if (!empty($_SESSION["platform_user_id"])) {
    $userId = (int) $_SESSION["platform_user_id"];
} elseif (!empty($bill["created_by_platform_user_id"])) {
    $userId = (int) $bill["created_by_platform_user_id"];
}

/* ============================================================
   3. Prevent duplicate posting
   ============================================================ */

$rsExisting = DB::Query("
    SELECT id, journal_no
    FROM journal_entry
    WHERE company_id = " . $companyId . "
      AND source_transaction_type = 'SUPPLIER_BILL'
      AND source_transaction_id = " . $supplierBillId . "
      AND posting_status = 'POSTED'
    LIMIT 1
");

$existing = $rsExisting->fetchAssoc();

if ($existing) {
    DB::Exec("
        UPDATE supplier_bill
        SET
            posting_status = 'POSTED',
            document_status = 'POSTED',
            posted_by_platform_user_id = " . $userId . ",
            posted_at = CURRENT_TIMESTAMP(6)
        WHERE id = " . $supplierBillId . "
        LIMIT 1
    ");

    $result["success"] = true;
    $result["message"] = "Supplier bill was already posted. Journal No: " . $existing["journal_no"];
    return;
}

/* ============================================================
   4. Helpers
   ============================================================ */

$getAccountId = function ($companyId, $accountCode) {
    $accountCode = db_addslashes($accountCode);

    $rs = DB::Query("
        SELECT id
        FROM chart_of_account
        WHERE company_id = " . (int)$companyId . "
          AND account_code = '" . $accountCode . "'
          AND status = 'ACTIVE'
        LIMIT 1
    ");

    $data = $rs->fetchAssoc();

    if ($data) {
        return (int) $data["id"];
    }

    return 0;
};

$fmt = function ($value) {
    return number_format((float)$value, 4, ".", "");
};

$nullableInt = function ($value) {
    if ($value === "" || $value === null) {
        return "NULL";
    }

    if ((int)$value <= 0) {
        return "NULL";
    }

    return (string)((int)$value);
};

$apAccountId = $getAccountId($companyId, "2100");
$inputVatAccountId = $getAccountId($companyId, "1200");
$defaultExpenseAccountId = $getAccountId($companyId, "5000");
$withholdingPayableAccountId = $getAccountId($companyId, "2300");

if ($apAccountId <= 0) {
    $result["message"] = "Accounts Payable account 2100 was not found.";
    return;
}

if ($defaultExpenseAccountId <= 0) {
    $result["message"] = "Purchases / Operating Expense account 5000 was not found.";
    return;
}

if ($headerInputVat > 0 && $inputVatAccountId <= 0) {
    $result["message"] = "Input VAT account 1200 was not found.";
    return;
}

if ($headerWithholding > 0 && $withholdingPayableAccountId <= 0) {
    $result["message"] = "Withholding Tax Payable account 2300 was not found.";
    return;
}

/* ============================================================
   5. Load Supplier Bill Detail
   ============================================================ */

$rsDetails = DB::Query("
    SELECT
        id,
        line_no,
        description,
        quantity,
        unit_price,
        gross_amount,
        discount_amount,
        net_before_tax,
        input_vat_amount,
        withholding_amount,
        line_total_amount,
        expense_account_id,
        asset_account_id,
        inventory_account_id,
        department_id,
        cost_center_id,
        project_id,
        project_task_id
    FROM supplier_bill_detail
    WHERE supplier_bill_id = " . $supplierBillId . "
    ORDER BY line_no, id
");

$detailRows = array();
$totalExpenseDebit = 0;
$totalInputVat = 0;
$totalWithholding = 0;

while ($detail = $rsDetails->fetchAssoc()) {

    $accountId = 0;

    if (!empty($detail["expense_account_id"])) {
        $accountId = (int) $detail["expense_account_id"];
    } elseif (!empty($detail["asset_account_id"])) {
        $accountId = (int) $detail["asset_account_id"];
    } elseif (!empty($detail["inventory_account_id"])) {
        $accountId = (int) $detail["inventory_account_id"];
    }

    if ($accountId <= 0) {
        $accountId = $defaultExpenseAccountId;
    }

    $expenseAmount = !empty($detail["net_before_tax"])
        ? (float) $detail["net_before_tax"]
        : ((float) $detail["gross_amount"] - (float) $detail["discount_amount"]);

    $expenseAmount = round($expenseAmount, 4);
    $vatAmount = round((float) $detail["input_vat_amount"], 4);
    $withholdingAmount = round((float) $detail["withholding_amount"], 4);

    if ($expenseAmount < 0) {
        $result["message"] = "Supplier bill detail has negative expense amount.";
        return;
    }

    $detailRows[] = array(
        "account_id" => $accountId,
        "description" => $detail["description"],
        "expense_amount" => $expenseAmount,
        "department_id" => $detail["department_id"],
        "cost_center_id" => $detail["cost_center_id"],
        "project_id" => $detail["project_id"],
        "project_task_id" => $detail["project_task_id"]
    );

    $totalExpenseDebit = round($totalExpenseDebit + $expenseAmount, 4);
    $totalInputVat = round($totalInputVat + $vatAmount, 4);
    $totalWithholding = round($totalWithholding + $withholdingAmount, 4);
}

if (count($detailRows) == 0) {
    $result["message"] = "Supplier bill has no detail lines.";
    return;
}

// If detail VAT is zero but header VAT has value, use header VAT.
if ($totalInputVat <= 0 && $headerInputVat > 0) {
    $totalInputVat = $headerInputVat;
}

// If detail withholding is zero but header withholding has value, use header withholding.
if ($totalWithholding <= 0 && $headerWithholding > 0) {
    $totalWithholding = $headerWithholding;
}

$totalDebit = round($totalExpenseDebit + $totalInputVat, 4);
$totalCredit = round($grandTotal + $totalWithholding, 4);

if (round($totalDebit, 4) != round($totalCredit, 4)) {
    $result["message"] =
        "Journal entry is not balanced. Debit: " .
        $fmt($totalDebit) .
        " Credit: " .
        $fmt($totalCredit);
    return;
}

/* ============================================================
   6. Create Journal Entry
   ============================================================ */

$rsUuid = DB::Query("SELECT UUID() AS uuid");
$dataUuid = $rsUuid->fetchAssoc();
$journalUuid = $dataUuid ? $dataUuid["uuid"] : "";

if ($journalUuid == "") {
    $result["message"] = "Unable to generate journal UUID.";
    return;
}

DB::Exec("START TRANSACTION");

DB::Exec("
    INSERT INTO journal_entry
    (
        journal_uuid,
        company_id,
        branch_id,
        accounting_period_id,
        journal_no,
        journal_date,
        journal_type,
        source_transaction_type,
        source_transaction_id,
        source_transaction_uuid,
        currency_code,
        exchange_rate,
        description,
        total_debit,
        total_credit,
        approval_status,
        posting_status,
        created_by_platform_user_id,
        posted_by_platform_user_id,
        posted_at
    )
    VALUES
    (
        '" . db_addslashes($journalUuid) . "',
        " . $companyId . ",
        " . $branchId . ",
        " . $accountingPeriodId . ",
        NULL,
        '" . $billDate . "',
        'PURCHASE',
        'SUPPLIER_BILL',
        " . $supplierBillId . ",
        '" . $billUuid . "',
        '" . $currencyCode . "',
        " . $fmt($exchangeRate) . ",
        'Posting for Supplier Bill " . $billNo . "',
        " . $fmt($totalDebit) . ",
        " . $fmt($totalCredit) . ",
        'APPROVED',
        'POSTED',
        " . $userId . ",
        " . $userId . ",
        CURRENT_TIMESTAMP(6)
    )
");

$rsJournal = DB::Query("
    SELECT id
    FROM journal_entry
    WHERE journal_uuid = '" . db_addslashes($journalUuid) . "'
    LIMIT 1
");

$journal = $rsJournal->fetchAssoc();

if (!$journal) {
    DB::Exec("ROLLBACK");
    $result["message"] = "Journal entry was not created.";
    return;
}

$journalEntryId = (int) $journal["id"];
$journalNo = "JE-" . date("Ym") . "-" . str_pad($journalEntryId, 6, "0", STR_PAD_LEFT);

DB::Exec("
    UPDATE journal_entry
    SET journal_no = '" . db_addslashes($journalNo) . "'
    WHERE id = " . $journalEntryId . "
    LIMIT 1
");

/* ============================================================
   7. Insert Journal Details
   ============================================================ */

$lineNo = 1;

foreach ($detailRows as $detailRow) {

    if ((float)$detailRow["expense_amount"] <= 0) {
        continue;
    }

    $desc = db_addslashes($detailRow["description"]);

    if ($desc == "") {
        $desc = "Supplier Bill Expense";
    }

    DB::Exec("
        INSERT INTO journal_entry_detail
        (
            journal_detail_uuid,
            journal_entry_id,
            line_no,
            account_id,
            description,
            debit_amount,
            credit_amount,
            foreign_debit_amount,
            foreign_credit_amount,
            department_id,
            cost_center_id,
            project_id,
            project_task_id
        )
        VALUES
        (
            UUID(),
            " . $journalEntryId . ",
            " . $lineNo . ",
            " . (int)$detailRow["account_id"] . ",
            'Expense - Supplier Bill " . $billNo . " - " . $desc . "',
            " . $fmt($detailRow["expense_amount"]) . ",
            0,
            " . $fmt($detailRow["expense_amount"]) . ",
            0,
            " . $nullableInt($detailRow["department_id"]) . ",
            " . $nullableInt($detailRow["cost_center_id"]) . ",
            " . $nullableInt($detailRow["project_id"]) . ",
            " . $nullableInt($detailRow["project_task_id"]) . "
        )
    ");

    $lineNo++;
}

// Debit Input VAT
if ($totalInputVat > 0) {
    DB::Exec("
        INSERT INTO journal_entry_detail
        (
            journal_detail_uuid,
            journal_entry_id,
            line_no,
            account_id,
            description,
            debit_amount,
            credit_amount,
            foreign_debit_amount,
            foreign_credit_amount
        )
        VALUES
        (
            UUID(),
            " . $journalEntryId . ",
            " . $lineNo . ",
            " . $inputVatAccountId . ",
            'Input VAT - Supplier Bill " . $billNo . "',
            " . $fmt($totalInputVat) . ",
            0,
            " . $fmt($totalInputVat) . ",
            0
        )
    ");

    $lineNo++;
}

// Credit Withholding Tax Payable
if ($totalWithholding > 0) {
    DB::Exec("
        INSERT INTO journal_entry_detail
        (
            journal_detail_uuid,
            journal_entry_id,
            line_no,
            account_id,
            description,
            debit_amount,
            credit_amount,
            foreign_debit_amount,
            foreign_credit_amount
        )
        VALUES
        (
            UUID(),
            " . $journalEntryId . ",
            " . $lineNo . ",
            " . $withholdingPayableAccountId . ",
            'Withholding Tax Payable - Supplier Bill " . $billNo . "',
            0,
            " . $fmt($totalWithholding) . ",
            0,
            " . $fmt($totalWithholding) . "
        )
    ");

    $lineNo++;
}

// Credit Accounts Payable
DB::Exec("
    INSERT INTO journal_entry_detail
    (
        journal_detail_uuid,
        journal_entry_id,
        line_no,
        account_id,
        description,
        debit_amount,
        credit_amount,
        foreign_debit_amount,
        foreign_credit_amount
    )
    VALUES
    (
        UUID(),
        " . $journalEntryId . ",
        " . $lineNo . ",
        " . $apAccountId . ",
        'Accounts Payable - Supplier Bill " . $billNo . "',
        0,
        " . $fmt($grandTotal) . ",
        0,
        " . $fmt($grandTotal) . "
    )
");

/* ============================================================
   8. Mark Supplier Bill as Posted
   ============================================================ */

DB::Exec("
    UPDATE supplier_bill
    SET
        posting_status = 'POSTED',
        document_status = 'POSTED',
        posted_by_platform_user_id = " . $userId . ",
        posted_at = CURRENT_TIMESTAMP(6)
    WHERE id = " . $supplierBillId . "
    LIMIT 1
");

DB::Exec("COMMIT");

$result["success"] = true;
$result["message"] = "Supplier bill posted successfully. Journal No: " . $journalNo;;

		RunnerContext::pop();
		echo runner_json_encode( $result);
		$button->deleteTempFiles();
	}

		// custom button - post_supplier_payment
	function buttonHandler_162625( $_params )
	{
		$result = array();

		$button = $this->prepareButtonContext( $_params );
		$ajax = $button; 
		$keys = $button->getKeys();

		// .NET naming
		$parameters = $_params;
		$params = &$_params;

		$result["success"] = false;
$result["message"] = "";

$supplierPaymentId = 0;

if (isset($params["keys"]["id"])) {
    $supplierPaymentId = (int)$params["keys"]["id"];
} elseif (isset($params["keys"][0]["id"])) {
    $supplierPaymentId = (int)$params["keys"][0]["id"];
} elseif (isset($params["keys"][0])) {
    $supplierPaymentId = (int)$params["keys"][0];
}

if ($supplierPaymentId <= 0) {
    $result["message"] = "Supplier payment ID was not found.";
    return;
}

$rs = DB::Query("CALL sp_post_supplier_payment(" . $supplierPaymentId . ")");

$row = $rs->fetchAssoc();

if (!$row) {
    $result["message"] = "Posting procedure did not return a result.";
    return;
}

$result["success"] = ((int)$row["success"] == 1);
$result["message"] = $row["message"];;

		RunnerContext::pop();
		echo runner_json_encode( $result);
		$button->deleteTempFiles();
	}

		// custom button - reverse_sales_invoice
	function buttonHandler_162627( $_params )
	{
		$result = array();

		$button = $this->prepareButtonContext( $_params );
		$ajax = $button; 
		$keys = $button->getKeys();

		// .NET naming
		$parameters = $_params;
		$params = &$_params;

		// BuildLedger Finance - Reverse Sales Invoice
// Safe version with duplicate reversal protection and clearer error handling.

$result["success"] = false;
$result["message"] = "";

$transactionStarted = false;

try {

    /* ============================================================
       1. Resolve Sales Invoice ID
       ============================================================ */

    $salesId = 0;

    if (isset($params["keys"]["id"])) {
        $salesId = (int)$params["keys"]["id"];
    } elseif (isset($params["keys"][0]["id"])) {
        $salesId = (int)$params["keys"][0]["id"];
    } elseif (isset($params["keys"][0])) {
        $salesId = (int)$params["keys"][0];
    } elseif (isset($keys["id"])) {
        $salesId = (int)$keys["id"];
    } elseif (isset($button)) {
        $currentRecord = $button->getCurrentRecord();
        if ($currentRecord && isset($currentRecord["id"])) {
            $salesId = (int)$currentRecord["id"];
        }
    }

    if ($salesId <= 0) {
        $result["message"] = "Sales invoice ID was not found.";
        return;
    }

    /* ============================================================
       2. Load Sales Invoice
       ============================================================ */

    $rsSales = DB::Query("
        SELECT
            id,
            sales_uuid,
            company_id,
            branch_id,
            accounting_period_id,
            document_no,
            transaction_date,
            customer_id,
            currency_code,
            exchange_rate,
            document_status,
            posting_status,
            created_by_platform_user_id
        FROM sales
        WHERE id = " . (int)$salesId . "
        LIMIT 1
    ");

    $sales = $rsSales->fetchAssoc();

    if (!$sales) {
        $result["message"] = "Sales invoice was not found.";
        return;
    }

    $documentStatus = strtoupper((string)$sales["document_status"]);
    $postingStatus = strtoupper((string)$sales["posting_status"]);

    /* ============================================================
       3. Check Existing Reversal First
       ============================================================ */

    $rsExisting = DB::Query("
        SELECT
            id,
            journal_no
        FROM journal_entry
        WHERE source_transaction_type = 'SALES_INVOICE_REVERSAL'
          AND source_transaction_id = " . (int)$salesId . "
          AND posting_status = 'POSTED'
        ORDER BY id DESC
        LIMIT 1
    ");

    $existing = $rsExisting->fetchAssoc();

    if ($documentStatus == "CANCELLED" || $documentStatus == "REVERSED") {
        if ($existing) {
            $result["success"] = true;
            $result["message"] = "This sales invoice is already cancelled/reversed. Reversal Journal No: " . $existing["journal_no"];
        } else {
            $result["message"] = "This sales invoice is already cancelled/reversed.";
        }
        return;
    }

    if ($existing) {
        DB::Exec("
            UPDATE sales
            SET
                document_status = 'CANCELLED',
                cancelled_at = CURRENT_TIMESTAMP(6)
            WHERE id = " . (int)$salesId . "
            LIMIT 1
        ");

        $result["success"] = true;
        $result["message"] = "This sales invoice was already reversed. Reversal Journal No: " . $existing["journal_no"];
        return;
    }

    if ($postingStatus != "POSTED") {
        $result["message"] = "Only posted sales invoices can be reversed.";
        return;
    }

    /* ============================================================
       4. Period Lock Check
       ============================================================ */

    $accountingPeriodId = (int)$sales["accounting_period_id"];

    $rsPeriod = DB::Query("
        SELECT period_status
        FROM accounting_period
        WHERE id = " . $accountingPeriodId . "
        LIMIT 1
    ");

    $period = $rsPeriod->fetchAssoc();

    if (!$period) {
        $result["message"] = "Accounting period was not found.";
        return;
    }

    if (strtoupper((string)$period["period_status"]) == "CLOSED") {
        $result["message"] = "This accounting period is already closed. This action is not allowed.";
        return;
    }

    /* ============================================================
       5. Block if Active Posted Payments Exist
       ============================================================ */

    $rsActivePayments = DB::Query("
        SELECT
            COALESCE(SUM(cpa.applied_amount), 0) AS active_applied_amount
        FROM customer_payment_application cpa
        INNER JOIN customer_payment cp
            ON cp.id = cpa.customer_payment_id
        WHERE cpa.sales_id = " . (int)$salesId . "
          AND cp.posting_status = 'POSTED'
          AND COALESCE(cp.document_status, '') <> 'CANCELLED'
    ");

    $activePayments = $rsActivePayments->fetchAssoc();
    $activeAppliedAmount = $activePayments ? round((float)$activePayments["active_applied_amount"], 4) : 0;

    if ($activeAppliedAmount > 0) {
        $result["message"] = "This sales invoice still has active posted payments. Reverse the customer payment first.";
        return;
    }

    /* ============================================================
       6. Find Original Sales Journal
       ============================================================ */

    $companyId = (int)$sales["company_id"];

    $rsOriginal = DB::Query("
        SELECT
            id,
            journal_no,
            total_debit,
            total_credit
        FROM journal_entry
        WHERE company_id = " . $companyId . "
          AND source_transaction_type = 'SALES_INVOICE'
          AND source_transaction_id = " . (int)$salesId . "
          AND posting_status = 'POSTED'
        ORDER BY id DESC
        LIMIT 1
    ");

    $original = $rsOriginal->fetchAssoc();

    if (!$original) {
        $result["message"] = "Original posted journal entry was not found.";
        return;
    }

    $originalJournalEntryId = (int)$original["id"];
    $originalJournalNo = db_addslashes((string)$original["journal_no"]);

    $totalDebit = round((float)$original["total_credit"], 4);
    $totalCredit = round((float)$original["total_debit"], 4);

    if ($totalDebit <= 0 || $totalCredit <= 0) {
        $result["message"] = "Original journal entry has invalid totals.";
        return;
    }

    if (round($totalDebit, 4) != round($totalCredit, 4)) {
        $result["message"] = "Reversal journal entry is not balanced.";
        return;
    }

    /* ============================================================
       7. Helper Functions
       ============================================================ */

    $fmt = function ($value) {
        return number_format((float)$value, 4, ".", "");
    };

    $nullableInt = function ($value) {
        if ($value === "" || $value === null) {
            return "NULL";
        }

        if ((int)$value <= 0) {
            return "NULL";
        }

        return (string)((int)$value);
    };

    /* ============================================================
       8. Prepare Values
       ============================================================ */

    $branchId = !empty($sales["branch_id"]) ? (int)$sales["branch_id"] : "NULL";
    $salesUuid = db_addslashes((string)$sales["sales_uuid"]);
    $documentNo = db_addslashes((string)$sales["document_no"]);

    $currencyCode = !empty($sales["currency_code"])
        ? db_addslashes((string)$sales["currency_code"])
        : "PHP";

    $exchangeRate = !empty($sales["exchange_rate"])
        ? (float)$sales["exchange_rate"]
        : 1;

    $userId = 0;

    if (!empty($_SESSION["platform_user_id"])) {
        $userId = (int)$_SESSION["platform_user_id"];
    } elseif (!empty($sales["created_by_platform_user_id"])) {
        $userId = (int)$sales["created_by_platform_user_id"];
    }

    $rsUuid = DB::Query("SELECT UUID() AS uuid");
    $dataUuid = $rsUuid->fetchAssoc();

    $journalUuid = $dataUuid ? $dataUuid["uuid"] : "";

    if ($journalUuid == "") {
        $result["message"] = "Unable to generate journal UUID.";
        return;
    }

    $temporaryJournalNo = "REV-PENDING-" . substr($journalUuid, 0, 8);
    $reversalDate = date("Y-m-d");

    /* ============================================================
       9. Start Transaction
       ============================================================ */

    DB::Exec("START TRANSACTION");
    $transactionStarted = true;

    /* ============================================================
       10. Create Reversal Journal Header
       ============================================================ */

    DB::Exec("
        INSERT INTO journal_entry
        (
            journal_uuid,
            company_id,
            branch_id,
            accounting_period_id,
            journal_no,
            journal_date,
            journal_type,
            source_transaction_type,
            source_transaction_id,
            source_transaction_uuid,
            currency_code,
            exchange_rate,
            description,
            total_debit,
            total_credit,
            approval_status,
            posting_status,
            created_by_platform_user_id,
            posted_by_platform_user_id,
            posted_at
        )
        VALUES
        (
            '" . db_addslashes($journalUuid) . "',
            " . $companyId . ",
            " . $branchId . ",
            " . $accountingPeriodId . ",
            '" . db_addslashes($temporaryJournalNo) . "',
            '" . $reversalDate . "',
            'REVERSAL',
            'SALES_INVOICE_REVERSAL',
            " . (int)$salesId . ",
            '" . $salesUuid . "',
            '" . $currencyCode . "',
            " . $fmt($exchangeRate) . ",
            'Reversal of Sales Invoice " . $documentNo . " / Original Journal " . $originalJournalNo . "',
            " . $fmt($totalDebit) . ",
            " . $fmt($totalCredit) . ",
            'APPROVED',
            'POSTED',
            " . $userId . ",
            " . $userId . ",
            CURRENT_TIMESTAMP(6)
        )
    ");

    $rsJournal = DB::Query("
        SELECT id
        FROM journal_entry
        WHERE journal_uuid = '" . db_addslashes($journalUuid) . "'
        LIMIT 1
    ");

    $journal = $rsJournal->fetchAssoc();

    if (!$journal) {
        DB::Exec("ROLLBACK");
        $transactionStarted = false;
        $result["message"] = "Reversal journal entry was not created.";
        return;
    }

    $journalEntryId = (int)$journal["id"];
    $journalNo = "JE-" . date("Ym") . "-" . str_pad($journalEntryId, 6, "0", STR_PAD_LEFT);

    DB::Exec("
        UPDATE journal_entry
        SET journal_no = '" . db_addslashes($journalNo) . "'
        WHERE id = " . $journalEntryId . "
        LIMIT 1
    ");

    /* ============================================================
       11. Reverse Original Journal Details
       ============================================================ */

    $rsDetails = DB::Query("
        SELECT
            line_no,
            account_id,
            description,
            debit_amount,
            credit_amount,
            foreign_debit_amount,
            foreign_credit_amount,
            customer_id,
            department_id,
            cost_center_id,
            project_id,
            project_task_id,
            sales_agent_id,
            campaign_id
        FROM journal_entry_detail
        WHERE journal_entry_id = " . $originalJournalEntryId . "
        ORDER BY line_no, id
    ");

    $lineNo = 1;
    $detailCount = 0;

    while ($detail = $rsDetails->fetchAssoc()) {

        $desc = db_addslashes((string)$detail["description"]);

        DB::Exec("
            INSERT INTO journal_entry_detail
            (
                journal_detail_uuid,
                journal_entry_id,
                line_no,
                account_id,
                description,
                debit_amount,
                credit_amount,
                foreign_debit_amount,
                foreign_credit_amount,
                customer_id,
                department_id,
                cost_center_id,
                project_id,
                project_task_id,
                sales_agent_id,
                campaign_id
            )
            VALUES
            (
                UUID(),
                " . $journalEntryId . ",
                " . $lineNo . ",
                " . (int)$detail["account_id"] . ",
                'REVERSAL - " . $desc . "',
                " . $fmt($detail["credit_amount"]) . ",
                " . $fmt($detail["debit_amount"]) . ",
                " . $fmt($detail["foreign_credit_amount"]) . ",
                " . $fmt($detail["foreign_debit_amount"]) . ",
                " . $nullableInt($detail["customer_id"]) . ",
                " . $nullableInt($detail["department_id"]) . ",
                " . $nullableInt($detail["cost_center_id"]) . ",
                " . $nullableInt($detail["project_id"]) . ",
                " . $nullableInt($detail["project_task_id"]) . ",
                " . $nullableInt($detail["sales_agent_id"]) . ",
                " . $nullableInt($detail["campaign_id"]) . "
            )
        ");

        $lineNo++;
        $detailCount++;
    }

    if ($detailCount <= 0) {
        DB::Exec("ROLLBACK");
        $transactionStarted = false;
        $result["message"] = "Original journal entry has no details to reverse.";
        return;
    }

    /* ============================================================
       12. Mark Sales Invoice as Cancelled
       ============================================================ */

    DB::Exec("
        UPDATE sales
        SET
            document_status = 'CANCELLED',
            cancelled_at = CURRENT_TIMESTAMP(6)
        WHERE id = " . (int)$salesId . "
        LIMIT 1
    ");

    DB::Exec("COMMIT");
    $transactionStarted = false;

    $result["success"] = true;
    $result["message"] = "Sales invoice reversed successfully. Reversal Journal No: " . $journalNo;

} catch (Exception $e) {

    if ($transactionStarted) {
        DB::Exec("ROLLBACK");
    }

    $result["success"] = false;
    $result["message"] = "Reverse sales invoice failed: " . $e->getMessage();
};

		RunnerContext::pop();
		echo runner_json_encode( $result);
		$button->deleteTempFiles();
	}

		// custom button - reverse_customer_payment
	function buttonHandler_162629( $_params )
	{
		$result = array();

		$button = $this->prepareButtonContext( $_params );
		$ajax = $button; 
		$keys = $button->getKeys();

		// .NET naming
		$parameters = $_params;
		$params = &$_params;

		$result["success"] = false;
$result["message"] = "";

$customerPaymentId = 0;

if (isset($params["keys"]["id"])) {
    $customerPaymentId = (int)$params["keys"]["id"];
} elseif (isset($params["keys"][0]["id"])) {
    $customerPaymentId = (int)$params["keys"][0]["id"];
} elseif (isset($params["keys"][0])) {
    $customerPaymentId = (int)$params["keys"][0];
}

if ($customerPaymentId <= 0) {
    $result["message"] = "Customer payment ID was not found.";
    return;
}

$rs = DB::Query("CALL sp_reverse_customer_payment(" . $customerPaymentId . ")");

$row = $rs->fetchAssoc();

if (!$row) {
    $result["message"] = "Reversal procedure did not return a result.";
    return;
}

$result["success"] = ((int)$row["success"] == 1);
$result["message"] = $row["message"];;

		RunnerContext::pop();
		echo runner_json_encode( $result);
		$button->deleteTempFiles();
	}

	
	// ajax code snippets
	
	
	// field events
	

	// code snippets
	






	// code snippet - Snippet1
	function event_162615(  ) {
		
		$rs = DB::Query("
    SELECT
        cash_in_bank,
        accounts_receivable,
        accounts_payable,
        total_revenue,
        total_expenses,
        net_income_loss,
        outstanding_ar,
        outstanding_ap,
        balance_check_difference,
        report_date
    FROM vw_finance_dashboard_summary
    ORDER BY report_date DESC
    LIMIT 1
");

$data = $rs ? $rs->fetchAssoc() : null;

if (!function_exists("blFinanceMoneyFormat")) {
    function blFinanceMoneyFormat($value) {
        return number_format((float)$value, 2);
    }
}

if ($data) {

    $cashInBank = blFinanceMoneyFormat($data["cash_in_bank"]);
    $accountsReceivable = blFinanceMoneyFormat($data["accounts_receivable"]);
    $accountsPayable = blFinanceMoneyFormat($data["accounts_payable"]);
    $totalRevenue = blFinanceMoneyFormat($data["total_revenue"]);
    $totalExpenses = blFinanceMoneyFormat($data["total_expenses"]);
    $netIncomeLoss = blFinanceMoneyFormat($data["net_income_loss"]);
    $outstandingAr = blFinanceMoneyFormat($data["outstanding_ar"]);
    $outstandingAp = blFinanceMoneyFormat($data["outstanding_ap"]);
    $balanceCheck = blFinanceMoneyFormat($data["balance_check_difference"]);

    $reportDate = "";
    if (!empty($data["report_date"])) {
        $reportDate = date("M d, Y", strtotime($data["report_date"]));
    }

    echo '
    <style>
        .bl-finance-dashboard-wrap {
            padding: 18px 20px 28px 20px;
            font-family: Arial, sans-serif;
        }

        .bl-finance-title {
            font-size: 28px;
            font-weight: 700;
            color: #1f2937;
            margin-bottom: 4px;
        }

        .bl-finance-subtitle {
            font-size: 14px;
            color: #6b7280;
            margin-bottom: 22px;
        }

        .bl-finance-grid {
            display: grid;
            grid-template-columns: repeat(4, minmax(190px, 1fr));
            gap: 16px;
            margin-bottom: 18px;
        }

        .bl-finance-card {
            background: #ffffff;
            border: 1px solid #e5e7eb;
            border-radius: 14px;
            padding: 18px 20px;
            box-shadow: 0 4px 14px rgba(0,0,0,0.06);
            min-height: 110px;
        }

        .bl-finance-label {
            font-size: 12px;
            color: #6b7280;
            font-weight: 700;
            text-transform: uppercase;
            letter-spacing: .04em;
            margin-bottom: 10px;
        }

        .bl-finance-value {
            font-size: 26px;
            font-weight: 800;
            color: #111827;
            line-height: 1.2;
        }

        .bl-finance-note {
            font-size: 12px;
            color: #9ca3af;
            margin-top: 7px;
        }

        .bl-finance-balance {
            max-width: 430px;
        }

        @media (max-width: 1200px) {
            .bl-finance-grid {
                grid-template-columns: repeat(2, minmax(190px, 1fr));
            }
        }

        @media (max-width: 700px) {
            .bl-finance-grid {
                grid-template-columns: 1fr;
            }
        }
    </style>

    <div class="bl-finance-dashboard-wrap">

        <div class="bl-finance-title">Finance Dashboard</div>
        <div class="bl-finance-subtitle">
            Summary of cash, receivables, payables, revenue, expenses, and balance check.
        </div>

        <div class="bl-finance-grid">

            <div class="bl-finance-card">
                <div class="bl-finance-label">Cash in Bank</div>
                <div class="bl-finance-value">₱'.$cashInBank.'</div>
                <div class="bl-finance-note">Available cash balance</div>
            </div>

            <div class="bl-finance-card">
                <div class="bl-finance-label">Accounts Receivable</div>
                <div class="bl-finance-value">₱'.$accountsReceivable.'</div>
                <div class="bl-finance-note">Customer receivables</div>
            </div>

            <div class="bl-finance-card">
                <div class="bl-finance-label">Accounts Payable</div>
                <div class="bl-finance-value">₱'.$accountsPayable.'</div>
                <div class="bl-finance-note">Supplier payables</div>
            </div>

            <div class="bl-finance-card">
                <div class="bl-finance-label">Net Income / Loss</div>
                <div class="bl-finance-value">₱'.$netIncomeLoss.'</div>
                <div class="bl-finance-note">Revenue less expenses</div>
            </div>

            <div class="bl-finance-card">
                <div class="bl-finance-label">Total Revenue</div>
                <div class="bl-finance-value">₱'.$totalRevenue.'</div>
                <div class="bl-finance-note">Posted revenue</div>
            </div>

            <div class="bl-finance-card">
                <div class="bl-finance-label">Total Expenses</div>
                <div class="bl-finance-value">₱'.$totalExpenses.'</div>
                <div class="bl-finance-note">Posted expenses</div>
            </div>

            <div class="bl-finance-card">
                <div class="bl-finance-label">Outstanding AR</div>
                <div class="bl-finance-value">₱'.$outstandingAr.'</div>
                <div class="bl-finance-note">Uncollected receivables</div>
            </div>

            <div class="bl-finance-card">
                <div class="bl-finance-label">Outstanding AP</div>
                <div class="bl-finance-value">₱'.$outstandingAp.'</div>
                <div class="bl-finance-note">Unpaid supplier bills</div>
            </div>

        </div>

        <div class="bl-finance-card bl-finance-balance">
            <div class="bl-finance-label">Balance Check</div>
            <div class="bl-finance-value">₱'.$balanceCheck.'</div>
            <div class="bl-finance-note">Report Date: '.$reportDate.'</div>
        </div>

    </div>
    ';

} else {
    echo "<div style='padding:20px; color:red;'>No finance dashboard data found.</div>";
}

echo '
<script>
$(document).ready(function () {

    setTimeout(function () {

        /* Hide remaining raw PHPRunner grid/table below the dashboard cards */
        $(".r-grid, .bs-grid, .table-responsive").each(function () {
            if ($(this).closest(".bl-finance-dashboard-wrap").length === 0) {
                $(this).hide();
            }
        });

        $("table").each(function () {
            if ($(this).closest(".bl-finance-dashboard-wrap").length === 0) {
                $(this).hide();
            }
        });

        /* Hide remaining list controls */
        $(".pagination, .bs-pagination").hide();
        $("[data-itemid=\'pagination\']").hide();
        $("[data-itemid=\'page_size\']").hide();
        $("[data-itemid=\'print_panel\']").hide();

        /* Replace project filename with clean app title */
        $("body").find("*").contents().filter(function () {
            return this.nodeType === 3 && this.nodeValue.indexOf("BuildLedger_Finance.phpr") >= 0;
        }).each(function () {
            this.nodeValue = this.nodeValue.replace("BuildLedger_Finance.phpr", "BuildLedger Finance");
        });

    }, 400);

});
</script>
';
		;
		
	}








}
?>
