<?php

class TransferRequest extends MY_Controller
{
    var $db;

    public function index()
    {
        echo "Goods Transfer Request/Requester API";
    }

    public function __construct()
    {
        parent::__construct();
    }

    public function getBranches()
    {
        try {
            if (!$this->isLogin) {
                $this->sys_error("Invalid Token");
                exit;
            }

            $prm = $this->sys_input;
            $regID = $prm["S_RegionalID"];

            $sql = "SELECT M_BranchID, M_BranchCode, M_BranchName
                    FROM m_branch
                    WHERE M_BranchIsActive = 'Y'
                    AND M_BranchS_RegionalID = ?";
            $qry = $this->db->query($sql, [$regID]);
            if (!$qry) {
                $this->sys_error_db("Failed to get Branches from RegionalID: {$regID}", $this->db);
                exit;
            }
            $rst = $qry->result_array();

            $this->sys_ok(["records" => $rst]);
        } catch (Exception $exc) {
            $message = $exc->getMessage();
            $this->sys_error($message);
        }
    }

    public function getStatus()
    {
        try {
            if (!$this->isLogin) {
                $this->sys_error("Invalid Token");
            }

            $rst = [
                ["value" => 0, "title" => "All"],
                ["value" => 1, "title" => "Draft"],
                ["value" => 2, "title" => "Pending"],
                ["value" => 3, "title" => "Approved"],
                ["value" => 4, "title" => "Rejected"],
                ["value" => 5, "title" => "Paid"],
                ["value" => 6, "title" => "Completed"]
            ];

            $this->sys_ok(["records" => $rst]);
        } catch (Exception $exc) {
            $message = $exc->getMessage();
            $this->sys_error($message);
            exit;
        }
    }

    public function search() {
        try {
            if (!$this->isLogin) {
                $this->sys_error("Invalid Token");
                exit;
            }

            $payload = $this->sys_input;
            $regionalID = $this->sys_user["S_RegionalID"];

            $startDate = $payload["startDate"];
            $endDate = $payload["endDate"];
            $search = $payload['search'];
            if ($search == "") {
                $search = "%";
            }

            $status = $payload['StatusName'];
            if (strtolower($status) == "all") {
                $status = "%";
            }

            $cabangcode = $payload["M_BranchCode"];
            if (strtolower($cabangcode) == "all") {
                $cabangcode = "%";
            }

            $offset = 0;
            $limit = 10;
            if ($payload['currentPage'] > 0) {
                $offset = ($payload['currentPage'] - 1) * $limit;
            }

            $sql = "SELECT
                t_goods_transfer.*,
                M_BranchID,
                M_BranchCode,
                M_BranchName
            FROM t_goods_transfer
            JOIN m_branch ON T_GoodsTransferToM_BranchCode = M_BranchCode 
                AND M_BranchIsActive = 'Y'
            WHERE T_GoodsTransferIsActive = 'Y'
                AND T_GoodsTransferFromS_RegionalID = ?
                AND T_GoodsTransferNum LIKE ?
                AND T_GoodsTransferToM_BranchCode LIKE ?
                AND T_GoodsTransferType = 'P'
                AND T_GoodsTransferStatus LIKE ?
                AND T_GoodsTransferDate BETWEEN DATE(?) AND DATE(?)
            ORDER BY T_GoodsTransferDate DESC";

            $sqldata = $sql . " LIMIT ? OFFSET ?";
            $quedata = $this->db->query($sqldata, [
                $regionalID, $search, $cabangcode, 
                $status, $startDate, $endDate,
                $limit, $offset
            ]);
            if (!$quedata) {
                $this->sys_error_db("[Error] get data");
                exit;
            }
            $data = $quedata->result_array();

            $sqltotal = "SELECT COUNT(*) AS total FROM ($sql) AS x";
            $quetotal = $this->db->query($sqltotal, [
                $regionalID, $search, $cabangcode, 
                $status, $startDate, $endDate
            ]);
            if (!$quetotal) {
                $this->sys_error_db("[Error] get total data");
                exit;
            }
            $total = $quetotal->result_array()[0]['total'];
            
            $result = array(
                "records" => $data,
                "total" => $total
            );
            $this->sys_ok($result);
        } catch (Exception $exc) {
            $message = $exc->getMessage();
            $this->sys_error($message);
        }
    }

    public function getUserLevel() {
        if (!$this->isLogin) {
            $this->sys_error("Invalid Token");
            exit;
        }
        $userId = $this->sys_user["M_UserID"];        

        $sqlevel = "SELECT * FROM m_user WHERE M_UserIsActive = 'Y' AND M_UserID = ?";
        $qulevel = $this->db->query($sqlevel, [$userId]);
        if (!$qulevel) {
            $this->sys_error_db("[Error] get approval level user");
            exit;
        }
        $data = $qulevel->row_array();

        $this->sys_ok($data);
    }

    public function createTfRequest() {
        try {
            if (!$this->isLogin) {
                throw new Exception("invalid token");
            }

            $user = $this->sys_user;
            $userID = $this->sys_user["M_UserID"];
            $data = $this->sys_input;


            // Check for debug mode
            $isDebug = isset($data['isDebug']) && $data['isDebug'] === true;
            
            if ($isDebug) {
                $this->db->trans_start(true); // Auto rollback mode
            } else {
                $this->db->trans_start();
            }

            // $sqlnum = "SELECT `fn_numbering`('TR') AS GoodsTfNum";
            // $quenum = $this->db->query($sqlnum, []);
            // if (!$quenum) {
            //     throw new Exception("[Error] Failed to generate transfer request number");
            // }
            // $goodsTfNum = $quenum->row()->GoodsTfNum;

            $areaid = $user['M_BranchID'];
            $areatype = 'B';
            if ($user['loginLevel'] == 'regional') {
                $areaid = $user['S_RegionalID'];
                $areatype = 'R';
            }

            $sqlusrdivisi = "SELECT M_UserDivisionDivisionID FROM m_userdivision 
                WHERE M_UserDivisionM_UserID = ? AND M_UserDivisionIsActive = 'Y'";
            $queusrdivisi = $this->db->query($sqlusrdivisi, [$user['M_UserID']]);
            if (!$queusrdivisi) {
                $this->db->trans_rollback();
                $this->sys_error_db("[Error] get user divisi", $this->db);
                exit;
            }
            $userDivID = $queusrdivisi->row_array()['M_UserDivisionDivisionID'];
            
            $sqlnum = "SELECT `fn_penomoran`(?, ?, ?, ?, ?, ?) AS numpd;";
            $quenum = $this->db->query($sqlnum, ['ST', $userDivID, $areatype, $areaid, 'SM', 'Y']);
            if (!$quenum) {
                throw new Exception("[Error] Failed generate number for TF request. " . json_encode($this->db->error()));
            }
            $goodsTfNum = $quenum->row_array()['numpd'];

            $sqlTG = "INSERT INTO t_goods_transfer (
                T_GoodsTransferFromS_RegionalID,
                T_GoodsTransferToM_BranchCode,
                T_GoodsTransferNum,
                T_GoodsTransferNotes,
                T_GoodsTransferDate,
                T_GoodsTransferType,
                T_GoodsTransferStatus,
                T_GoodsTransferIsActive,
                T_GoodsTransferCreatedAt,
                T_GoodsTransferM_UserID
            ) VALUES (?,?,?,?,?,?,?,'Y',NOW(),?)";
            $queTG = $this->db->query($sqlTG, [
                $data["S_RegionalID"],
                $data["M_BranchCode"],
                $goodsTfNum,
                $data["GoodsTfNotes"],
                $data["GoodsTfDate"],
                'P',
                $data["GoodsTfStatus"],
                $userID
            ]);
            if (!$queTG) {
                throw new Exception("[Error] Failed to insert into table t_goods_transfer. " . json_encode($this->db->error()));
            }
            $tGoodsTFID = $this->db->insert_id();

            $datadetail = $data['detail'];
            foreach ($datadetail as $key => $value) {
                $flagID = $value['PurchaseRequestFlagID'];
                $itemID = $value['PurchaseRequestDetailM_ItemID'];
                $unitID = $value['PurchaseRequestDetailItemUnitID'];
                $QtyStart = intval($value['PurchaseRequestFlagQty']);
                $QtyTfRes = intval($value['PurchaseRequestFlagQtyRest']);

                // encode json list batch number
                $jsonBatchNo = "[]";
                if (isset($value['ItemRequest']) && is_array($value['ItemRequest'])) {
                    $batches = $value['ItemRequest'];
                    if (sizeof($batches) > 0) {
                        $jsonResult = json_encode($batches);
                        if ($jsonResult === false) {
                            throw new Exception("[Error] Failed to encode json batch number");
                        } else {
                            $jsonBatchNo = $jsonResult;
                        }
                    }
                }

                $sqlTGD = "INSERT INTO t_goods_transfer_detail (
                    T_GoodsTransferDetailIDT_GoodsTransferID,
                    T_GoodsTransferDetailT_PurchaseRequestFlagID,
                    T_GoodsTransferDetailM_ItemID,
                    T_GoodsTransferDetailItemUnitID,
                    T_GoodsTransferDetailItemBatchNo,
                    T_GoodsTransferDetailQty,
                    T_GoodsTransferDetailQtyTF,
                    T_GoodsTransferDetailCreatedAt,
                    T_GoodsTransferDetailM_UserID 
                ) VALUES (?,?,?,?,?,?,?,NOW(),?)";
                $queTGD = $this->db->query($sqlTGD, [
                    $tGoodsTFID, $flagID, $itemID, $unitID, 
                    $jsonBatchNo, $QtyStart, $QtyTfRes, $userID
                ]);
                if (!$queTGD) {
                    throw new Exception("[Error] Failed to insert TF goods detail. " . json_encode($this->db->error()));
                }
                $TGoodsTFDetailID = $this->db->insert_id();
                $datadetail[$key]['TGoodsTFDetailID'] = $TGoodsTFDetailID;

                // jika status verified
                if ($data["GoodsTfStatus"] == 'Verified') {
                    
                    $this->moveStockToTransit($value, $data["S_RegionalID"], $userID, $TGoodsTFDetailID);

                    $sqlUpdFlag = "UPDATE purchase_request_flag 
                    SET PurchaseRequestFlagQtyRest = ?,
                        PurchaseRequestFlagQtyProses = PurchaseRequestFlagQtyProses + ?
                    WHERE PurchaseRequestFlagID = ? 
                        AND PurchaseRequestFlagIsActive = 'Y'";
                    $queUpdFlag = $this->db->query($sqlUpdFlag, [$QtyTfRes, $QtyTfRes, $flagID]);
                    if (!$queUpdFlag) {
                        throw new Exception("[Error] Failed to update quantity in table purchase_request_flag. " . json_encode($this->db->error()));
                    }
                }
            }

            // get last inserted data
            $sqltfgood = "SELECT * FROM t_goods_transfer WHERE T_GoodsTransferIsActive = 'Y' AND T_GoodsTransferID = ?";
            $quetfgood = $this->db->query($sqltfgood, [$tGoodsTFID]);
            if (!$quetfgood) {
                throw new Exception("[Error] Failed to get last inserted transfer data. " . json_encode($this->db->error()));
            }
            $lastTFData = $quetfgood->row_array();

            $sqltfdetail = "SELECT * FROM t_goods_transfer_detail WHERE T_GoodsTransferDetailIsActive = 'Y' AND T_GoodsTransferDetailIDT_GoodsTransferID = ?";
            $quetfdetail = $this->db->query($sqltfdetail, [$tGoodsTFID]);
            if (!$quetfdetail) {
                throw new Exception("[Error] Failed to get last inserted transfer detail data. " . json_encode($this->db->error()));
            }
            $lastTFDetail = $quetfdetail->result_array();
            $lastTFData['detail'] = $lastTFDetail;

            $msg = "Transfer Request nomor: {$lastTFData['T_GoodsTransferNum']}, tanggal: {$lastTFData['T_GoodsTransferDate']}, type: {$lastTFData['T_GoodsTransferType']}";
            $this->insertUserActivty($tGoodsTFID, "CREATE", $userID, $msg, json_encode($lastTFData));

            $this->db->trans_complete();
            $result = array(
                "total" => 1,
                "msg" => $msg,
                "records" => $lastTFData
            );
            $this->sys_ok($result);

        } catch (Exception $exc) {
            $message = $exc->getMessage();
            $this->db->trans_rollback();
            $this->sys_error($message);
        }
    }

    public function updateTfRequest() {
        try {
            if (!$this->isLogin) {
                throw new Exception("Invalid Token");
            }

            $user = $this->sys_user;
            $userID = $this->sys_user["M_UserID"];
            $data = $this->sys_input;

            $this->db->trans_begin();

            $sqlTG = "UPDATE t_goods_transfer SET 
                T_GoodsTransferFromS_RegionalID = ?,
                T_GoodsTransferToM_BranchCode = ?,
                T_GoodsTransferNum = ?,
                T_GoodsTransferNotes = ?,
                T_GoodsTransferDate = ?,
                T_GoodsTransferStatus = ?,
                T_GoodsTransferLastUpdated = NOW(),
                T_GoodsTransferM_UserID = ?
            WHERE T_GoodsTransferID = ?
            AND T_GoodsTransferIsActive = 'Y'";
            $queTG = $this->db->query($sqlTG, [
                $data["S_RegionalID"],
                $data["M_BranchCode"],
                $data["GoodsTfNum"],
                $data["GoodsTfNotes"],
                $data["GoodsTfDate"],
                $data["GoodsTfStatus"],
                $userID,
                $data["GoodsTfID"]
            ]);
            if (!$queTG) {
                throw new Exception("[Error] Failed to update table t_goods_transfer. " . json_encode($this->db->error()));
            }

            // approve or verif tf request
            if ($data["GoodsTfStatus"] == 'Verified' || $data["GoodsTfStatus"] == 'Approved') {
                $this->toVerifOrApprove($data["GoodsTfID"], $user);
            }

            $sqlUpdToN = "UPDATE t_goods_transfer_detail SET T_GoodsTransferDetailIsActive = 'N' WHERE T_GoodsTransferDetailIDT_GoodsTransferID = ?";
            $queUpdToN = $this->db->query($sqlUpdToN, [$data["GoodsTfID"]]);
            if (!$queUpdToN) {
                throw new Exception("[Error] Failed to update all detail records to inactive. " . json_encode($this->db->error()));
            }

            $datadetail = $data['detail'];
            foreach ($datadetail as $key => $value) {
                $flagID = $value['PurchaseRequestFlagID'];
                $itemID = $value['PurchaseRequestDetailM_ItemID'];
                $unitID = $value['PurchaseRequestDetailItemUnitID'];
                $QtyStart = intval($value['PurchaseRequestFlagQty']);
                $QtyTfRes = intval($value['PurchaseRequestFlagQtyRest']);

                // encode json list batch number
                $jsonBatchNo = "[]";
                if (isset($value['ItemRequest']) && is_array($value['ItemRequest'])) {
                    $batches = $value['ItemRequest'];
                    if (sizeof($batches) > 0) {
                        $jsonResult = json_encode($batches);
                        if ($jsonResult === false) {
                            throw new Exception("[Error] Failed to encode json batch number");
                        } else {
                            $jsonBatchNo = $jsonResult;
                        }
                    }
                }

                $sqlTGD = "INSERT INTO t_goods_transfer_detail (
                    T_GoodsTransferDetailIDT_GoodsTransferID,
                    T_GoodsTransferDetailT_PurchaseRequestFlagID,
                    T_GoodsTransferDetailM_ItemID,
                    T_GoodsTransferDetailItemUnitID,
                    T_GoodsTransferDetailItemBatchNo,
                    T_GoodsTransferDetailQty,
                    T_GoodsTransferDetailQtyTF,
                    T_GoodsTransferDetailCreatedAt,
                    T_GoodsTransferDetailM_UserID 
                ) VALUES (?,?,?,?,?,?,?,NOW(),?)";
                $queTGD = $this->db->query($sqlTGD, [
                    $data["GoodsTfID"], $flagID, $itemID, $unitID, 
                    $jsonBatchNo, $QtyStart, $QtyTfRes, $userID
                ]);
                if (!$queTGD) {
                    throw new Exception("[Error] Failed to insert TF goods detail. " . json_encode($this->db->error()));
                }
                $TGoodsTFDetailID = $this->db->insert_id();
                $datadetail[$key]['TGoodsTFDetailID'] = $TGoodsTFDetailID;

                // jika status verified
                if ($data["GoodsTfStatus"] == 'Verified') {

                    $this->moveStockToTransit($value, $data["S_RegionalID"], $userID, $TGoodsTFDetailID);

                    $sqlUpdFlag = "UPDATE purchase_request_flag 
                    SET PurchaseRequestFlagQtyRest = ?,
                        PurchaseRequestFlagQtyProses = PurchaseRequestFlagQtyProses + ?
                    WHERE PurchaseRequestFlagID = ? 
                        AND PurchaseRequestFlagIsActive = 'Y'";
                    $queUpdFlag = $this->db->query($sqlUpdFlag, [$QtyTfRes, $QtyTfRes, $flagID]);
                    if (!$queUpdFlag) {
                        throw new Exception("[Error] Failed to update quantity in table purchase_request_flag. " . json_encode($this->db->error()));
                    }
                }
            }

            // get last inserted data
            $sqltfgood = "SELECT * FROM t_goods_transfer WHERE T_GoodsTransferIsActive = 'Y' AND T_GoodsTransferID = ?";
            $quetfgood = $this->db->query($sqltfgood, [$data["GoodsTfID"]]);
            if (!$quetfgood) {
                throw new Exception("[Error] Failed to get updated transfer data. " . json_encode($this->db->error()));
            }
            $lastTFData = $quetfgood->row_array();

            $sqltfdetail = "SELECT * FROM t_goods_transfer_detail WHERE T_GoodsTransferDetailIsActive = 'Y' AND T_GoodsTransferDetailIDT_GoodsTransferID = ?";
            $quetfdetail = $this->db->query($sqltfdetail, [$data["GoodsTfID"]]);
            if (!$quetfdetail) {
                throw new Exception("[Error] Failed to get updated transfer detail data. " . json_encode($this->db->error()));
            }
            $lastTFDetail = $quetfdetail->result_array();
            $lastTFData['detail'] = $lastTFDetail;

            $msg = "Transfer Request nomor: {$lastTFData['T_GoodsTransferNum']}, tanggal: {$lastTFData['T_GoodsTransferDate']}, type: {$lastTFData['T_GoodsTransferType']}";
            $this->insertUserActivty($data["GoodsTfID"], "UPDATE", $userID, $msg, json_encode($lastTFData));

            $this->db->trans_commit();
            $result = array(
                "total" => 1,
                "msg" => $msg,
                "records" => $lastTFData
            );
            $this->sys_ok($result);
        } catch (Exception $exc) {
            $message = $exc->getMessage();
            $this->db->trans_rollback();
            $this->sys_error($message);
        }
    }

    /**
     * * Pindah Stock ke WH Transit jika Verified 
     * @param array $detailTf - The detail of the transfer with ItemRequest info
     * @param int $regionalID - The regional ID to find the appropriate transit warehouse
     * @param int $userID - The user ID performing the operation
     * @param int $TGoodsTFDetailID - The ID of the transfer detail
     * @return void
     * @throws Exception if any validation or database operation fails
     */
    private function moveStockToTransit($detailTf, $regionalID, $userID,$TGoodsTFDetailID){
        //* Validasi StockQty bisa memenuhi
        foreach ($detailTf['ItemRequest'] as $itemRequest) {
            $stockID    = $itemRequest['StockID'];
            $qtyReq     = intval($itemRequest['QtyReq']);
            
            $sqlCheck   = "SELECT StockQty FROM stock WHERE StockID = ?";
            $qryCheck   = $this->db->query($sqlCheck, [$stockID]);

            if (!$qryCheck || $qryCheck->num_rows() === 0) {
                $err = json_encode($this->db->error());
                throw new Exception($err);
            }

            if($qryCheck->row()->StockQty < $qtyReq){
                $sqlItem    = "SELECT M_ItemDesc, ItemUnitName 
                        FROM stock 
                        JOIN m_item ON M_ItemID = StockItemID 
                        JOIN itemunit ON ItemUnitID = StockItemUnitID 
                        WHERE StockID = ?";
                $qryItem    = $this->db->query($sqlItem, [$stockID]);
                $itemInfo   = $qryItem->row_array();
                
                throw new Exception("Err: Perhatikan Qty Stock dan Request. Tidak bisa verifikasi karena Stock Item {$itemInfo['M_ItemDesc']} 
                tersisa " . ($qryCheck ? $qryCheck->row()->StockQty : 0) . " {$itemInfo['ItemUnitName']}, 
                sedangkan request {$qtyReq} {$itemInfo['ItemUnitName']}");
            }
        }

        //* Query Warehouse Transit where WHRegionalID == $data[S_RegionalID]
        $sqlWh = "SELECT WarehouseID FROM warehouse WHERE WarehouseS_RegionalID = ? 
                AND WarehouseIsTransit = 'Y' AND WarehouseIsActive = 'Y' 
                AND WarehouseType = 'R' ORDER BY WarehouseID DESC LIMIT 1";
        $qryWh = $this->db->query($sqlWh, [$regionalID]);
        if (!$qryWh || $qryWh->num_rows() === 0) {
            throw new Exception("Transit warehouse not found for Regional ID: {$regionalID}");
        }
        $transitWhId = $qryWh->row()->WarehouseID;

        foreach($detailTf['ItemRequest'] as $itemReq){
            $stockID        = $itemReq['StockID'];
            $qtyReq         = intval($itemReq['QtyReq']);
            $stockNumber    = $itemReq['StockStockNumber'];
            $batchNo        = $itemReq['StockBatchNo'];
            $itemID         = $itemReq['StockItemID'];
            $unitID         = $itemReq['StockItemUnitID'];
            $itemPrice      = $itemReq['StockItemPrice'];
            $expDate        = $itemReq['StockED'];
            $warehouseID    = $itemReq['WarehouseID'];
            $lastSourceStockQty   = $itemReq['StockQty'];

            //* Update Kurangi Existing Stock Qty
            $sqlUpdt = "UPDATE stock SET StockQty = StockQty - ?, 
                        StockLastUpdated = NOW(), StockUserID = ?
                        WHERE StockID = ?";
            $qryUpdt = $this->db->query($sqlUpdt, [$qtyReq, $userID, $stockID]);
            if (!$qryUpdt) {
                throw new Exception("Failed to update source stock quantity (ID: {$stockID})");
            }

            //* Create / Update Transit Stock
            $sqlCekTransit = "SELECT StockID FROM stock 
                        WHERE StockWarehouseID = ? 
                        AND StockItemID = ?  AND StockItemUnitID = ? 
                        AND StockBatchNo = ? ORDER BY StockID DESC LIMIT 1";
            $qryCekTransit = $this->db->query($sqlCekTransit, [
                $transitWhId, $itemID, $unitID, $batchNo
            ]);
            if (!$qryCekTransit) {
                $err = json_encode($this->db->error());
                throw new Exception("Failed to cek stock on warehouseId (ID: {$transitWhId}). DB Error: $err");
            }

            // *Insert or Update Row Stock Warehouse Transit
            if ($qryCekTransit->num_rows() > 0) {
                $transitStockID = $qryCekTransit->row()->StockID;
                
                $sqlUpdTransit  = "UPDATE stock SET StockQty = StockQty + ?,
                        StockLastUpdated = NOW(), StockUserID = ?
                        WHERE StockID = ?";
                $qryUpdTransit  = $this->db->query($sqlUpdTransit, [
                    $qtyReq, $userID, $transitStockID
                ]);
                if (!$qryUpdTransit) {
                    throw new Exception("Failed to update transit stock quantity (ID: {$transitStockID})");
                }
            } else {
            $sqlInsTransit = "INSERT INTO stock (
                StockWarehouseID, StockItemID, StockItemUnitID, 
                StockBatchNo, StockQty, StockStockNumber, 
                StockItemPrice, StockED, 
                StockLastUpdated, StockUserID
            ) VALUES (?, ?, ?, ?, ?, fn_numbering('SN'), ?, ?, NOW(), ?)";
            $qryInsTransit = $this->db->query($sqlInsTransit, [
                $transitWhId, $itemID, $unitID,
                $batchNo, $qtyReq, 
                $itemPrice, $expDate, $userID
            ]);
            if (!$qryInsTransit) {
                throw new Exception("Failed to insert new transit stock for Item ID: {$itemID}");
            }

                $transitStockID = $this->db->insert_id();
            }

            //* Log Stock Movement (WTT - Warehouse To Transit)
            // Log hanya dari sumber ke transit
            $sqlLog = "INSERT INTO stocklog (
                    StockLogWarehouseID, StockLogDateTime, StockLogItemID,
                    StockLogItemUnitID, StockLogStockNumber, StockLogBatchNo,
                    StockLogED, StockLogReffID, StockLogStatus, StockLogQty, StockLogUserID)
                    VALUES (?, NOW(), ?, ?, ?, ?, ?, ?, 'WTT', ?, ?)";
            $qryLog = $this->db->query($sqlLog, [
                $warehouseID, $itemID, $unitID, 
                $stockNumber, $batchNo, $expDate, 
                $TGoodsTFDetailID, $qtyReq, $userID
            ]);
            if (!$qryLog) {
                throw new Exception("Failed to log source warehouse movement");
            }

            // * Query Stockcard where StockCardReffID == $stockID ORDER BY StockCardID DESC LIMIT 1
            // * Insert Stockcard WH Sumber ke Transit (OUT)
            // StockCardBefore = Previous StockCardAfter, StockCardIn=0, StockCardOut= qtyReg, StockCardAfter = PreviousCardAfter - qtyReq
            $sqlLastSourceCard = "SELECT StockCardAfter FROM stockcard 
                    WHERE StockCardReffID = ? ORDER BY StockCardID DESC LIMIT 1";
            $qryLastSourceCard = $this->db->query($sqlLastSourceCard, [$stockID]);
            if (!$qryLastSourceCard) {
                throw new Exception("Failed to query last stockcard entry for source stock ID: {$stockID}");
            }

            $beforeQty = 0;
            if ($qryLastSourceCard->num_rows() > 0) {
                $beforeQty = $qryLastSourceCard->row()->StockCardAfter;
            } else {
                $beforeQty = $lastSourceStockQty;
            }

            $afterQty = $beforeQty - $qtyReq;

            // Insert Stockcard for source warehouse (OUT)
            $sqlInsSourceCard = "INSERT INTO stockcard (
                StockCardWarehouseID, StockCardDatetime, StockCardItemID, 
                StockCardItemUnitID, StockCardBatchNo, StockCardED, 
                StockCardReffID, StockCardStatus, 
                StockCardBefore, StockCardIn, StockCardOut, StockCardAfter, 
                StockCardUserID
            ) VALUES (?, NOW(), ?, ?, ?, ?, ?, 'MWT', ?, 0, ?, ?, ?)";

            $qryInsSourceCard = $this->db->query($sqlInsSourceCard, [
                $warehouseID, $itemID, $unitID, $batchNo, $expDate, 
                $stockID, $beforeQty, $qtyReq, $afterQty, $userID
            ]);

            if (!$qryInsSourceCard) {
                throw new Exception("Failed to insert stockcard for source warehouse ID: {$warehouseID}");
            }
            
            // * Query Stockcard where StockCardReffID == $transitStockID ORDER BY StockCardID DESC LIMIT 1
            // * Insert Stockcard WH Transit (IN)
            // New row with StockReffID = $transitStockID, StockCardIn = qtyReq, StockCardOut = 0, StockCardAfter = PreviousCardAfter + qtyReq
            $sqlLastTransitCard = "SELECT StockCardAfter FROM stockcard 
                    WHERE StockCardReffID = ? ORDER BY StockCardID DESC LIMIT 1";
            $qryLastTransitCard = $this->db->query($sqlLastTransitCard, [$transitStockID]);
            if (!$qryLastTransitCard) {
                throw new Exception("Failed to query last stockcard entry for transit stock ID: {$transitStockID}");
            }

            $transitBeforeQty = 0;
            if ($qryLastTransitCard->num_rows() > 0) {
                $transitBeforeQty = $qryLastTransitCard->row()->StockCardAfter;
            } else {
                $sqlTransitQty = "SELECT StockQty FROM stock WHERE StockID = ?";
                $qryTransitQty = $this->db->query($sqlTransitQty, [$transitStockID]);
                if (!$qryTransitQty || $qryTransitQty->num_rows() === 0) {
                    throw new Exception("Failed to get current stock quantity for transit stock ID: {$transitStockID}");
                }
                $transitBeforeQty = $qryTransitQty->row()->StockQty - $qtyReq; // Subtract qtyReq since we've already added it
            }

            $transitAfterQty = $transitBeforeQty + $qtyReq;

            // Insert Stockcard for transit warehouse (IN)
            $sqlInsTransitCard = "INSERT INTO stockcard (
                StockCardWarehouseID, StockCardDatetime, StockCardItemID, 
                StockCardItemUnitID, StockCardBatchNo, StockCardED, 
                StockCardReffID, StockCardStatus, 
                StockCardBefore, StockCardIn, StockCardOut, StockCardAfter, StockCardUserID
            ) VALUES (?, NOW(), ?, ?, ?, ?, ?, 'MWF', ?, ?, 0, ?, ?)";

            $qryInsTransitCard = $this->db->query($sqlInsTransitCard, [
                $transitWhId, $itemID, $unitID, $batchNo, $expDate, 
                $transitStockID, $transitBeforeQty, $qtyReq, $transitAfterQty, $userID
            ]);

            if (!$qryInsTransitCard) {
                throw new Exception("Failed to insert stockcard for transit warehouse ID: {$transitWhId}");
            }
        }

        // Return true to indicate success
        return true;
    }

    public function deleteTfRequest()
    {
        try {
            if (!$this->isLogin) {
                $this->sys_error("Invalid Token");
            }
            $userID = $this->sys_user["M_UserID"];
            $goodsTfID = $this->sys_input["GoodsTfID"];

            $this->db->trans_begin();

            $sql = "UPDATE t_goods_transfer SET 
                    T_GoodsTransferIsActive = 'N',
                    T_GoodsTransferDeleted = NOW(),
                    T_GoodsTransferM_UserID = ?
                WHERE T_GoodsTransferID = ?
                AND T_GoodsTransferIsActive = 'Y'";
            $qry = $this->db->query($sql, [$userID, $goodsTfID]);
            if (!$qry) {
                $this->db->trans_rollback();
                $this->sys_error_db("Gagal menghapus Transfer Request (ID: {$goodsTfID}). Error:", $this->db);
                exit;
            }

            $sqldet = "UPDATE t_goods_transfer_detail SET
                    T_GoodsTransferDetailIsActive = 'N',
                    T_GoodsTransferDetailDeletedAt = NOW(),
                    T_GoodsTransferDetailM_UserID = ?
                WHERE T_GoodsTransferDetailIDT_GoodsTransferID = ? AND T_GoodsTransferDetailIsActive = 'Y'
            ";
            $qdet = $this->db->query($sqldet, [$userID, $goodsTfID]);
            if (!$qdet) {
                $this->db->trans_rollback();
                $this->sys_error_db("failed delete detail transfer request item. Error:", $this->db);
                exit;
            }

            $sql_json = "SELECT t_goods_transfer.*, '' as details
            FROM t_goods_transfer
            WHERE T_GoodsTransferID = ?";
            $qry_json = $this->db->query($sql_json, [$goodsTfID]);
            if (!$qry_json) {
                $this->db->trans_rollback();
                $this->sys_error_db("select json tf error");
                exit;
            }
            $row_json = $qry_json->row_array();

            $descActivity = "Hapus transfer request {$row_json['T_GoodsTransferNum']}";

            $sql_json_detail = "SELECT t_goods_transfer_detail.* 
                FROM t_goods_transfer_detail
                WHERE T_GoodsTransferDetailIDT_GoodsTransferID = ?";
            $qry_json_detail = $this->db->query($sql_json_detail, [$goodsTfID]);
            if ($qry_json_detail) {
                $row_json["details"] = $qry_json_detail->result_array();
            } else {
                $this->sys_error_db("select detail tf error", $this->db);
                exit;
            }

            $data_json = $row_json;

            $this->insertUserActivty($goodsTfID, 'DELETE', $userID, $descActivity, json_encode($data_json));
            $this->db->trans_commit();

            $this->sys_ok("Berhasil menghapus Transfer Request (ID: {$goodsTfID})");
        } catch (Exception $exc) {
            $message = $exc->getMessage();
            $this->sys_error($message);
        }
    }

    public function getListDetail()
    {
        try {
            if (!$this->isLogin) {
                $this->sys_error("Invalid Token");
                exit;
            }

            $prm = $this->sys_input;
            $regionalid = $prm['regionalid'];
            $branchIDOrigin = $prm['branchIDorigin'];
            $branchCodeDestin = $prm['branchcodedestin'];
            $currpage = $prm['currpage'];

            $page = 0;
            $limit = 5;
            if ($currpage > 0) {
                $page = ($currpage - 1) * $limit;
            }

            // Get total count (unchanged)
            $sqltotal = "SELECT 
                COUNT(DISTINCT PurchaseRequestDetailID) as total
            FROM purchase_request_flag 
            JOIN purchase_request_detail ON PurchaseRequestDetailID = PurchaseRequestFlagPurchaseRequestDetailID
                AND PurchaseRequestDetailIsActive = 'Y'
            JOIN purchase_request ON PurchaseRequestID = PurchaseRequestDetailPurchaseRequestID 
                AND PurchaseRequestIsActive = 'Y' 
            JOIN m_item ON M_ItemID = PurchaseRequestDetailM_ItemID AND M_ItemIsActive = 'Y'
                AND M_ItemItem_CategoryID = 1
            WHERE PurchaseRequestFlagIsActive = 'Y' 
                AND FIND_IN_SET('TF', PurchaseRequestFlagStatus) 
                AND PurchaseRequestDetailStatus = 'Read' 
                AND PurchaseRequestFlagIsClosed = 'N' 
                AND PurchaseRequestS_RegionalID = ? 
                AND PurchaseRequestM_BranchCode = ? 
                AND NOT EXISTS (
                    SELECT 1 
                    FROM t_goods_transfer 
                    JOIN t_goods_transfer_detail ON T_GoodsTransferDetailIDT_GoodsTransferID = T_GoodsTransferID 
                        AND T_GoodsTransferDetailIsActive = 'Y' 
                    WHERE T_GoodsTransferDetailT_PurchaseRequestFlagID = PurchaseRequestFlagID
                        AND T_GoodsTransferFromS_RegionalID = ? 
                        AND T_GoodsTransferToM_BranchCode = ? 
                )";
            $quetotal = $this->db->query($sqltotal, [
                $regionalid, $branchCodeDestin, 
                $regionalid, $branchCodeDestin
            ]);
            if (!$quetotal) {
                $this->sys_error_db("error get total listing tf request");
                exit;
            }
            $total = $quetotal->result_array()[0]['total'];
            
            if (intval($total) === 0) {
                $this->sys_ok(["records" => [], "total" => $total]);
                exit;
            }

            // Simplified main query - removing stock-related JOINs
            $sqlist = "SELECT
                PurchaseRequestDate,
                PurchaseRequestNumber,
                PurchaseRequestDetailM_ItemID,
                PurchaseRequestDetailItemUnitID,
                M_ItemDesc,
                unitreq.ItemUnitID as RequestItemUnitID,
                unitreq.ItemUnitName as RequestItemUnitName,
                PurchaseRequestFlagID,
                PurchaseRequestFlagQty,
                PurchaseRequestFlagQtyRest,
                PurchaseRequestFlagStatus
            FROM purchase_request_flag 
            JOIN purchase_request_detail ON PurchaseRequestDetailID = PurchaseRequestFlagPurchaseRequestDetailID
                AND PurchaseRequestDetailIsActive = 'Y'
            JOIN purchase_request ON PurchaseRequestID = PurchaseRequestDetailPurchaseRequestID 
                AND PurchaseRequestIsActive = 'Y' 
            JOIN m_item ON M_ItemID = PurchaseRequestDetailM_ItemID AND M_ItemIsActive = 'Y' 
                AND M_ItemItem_CategoryID = 1
            JOIN itemunit unitreq
                ON unitreq.ItemUnitID = PurchaseRequestDetailItemUnitID
                AND unitreq.ItemUnitIsActive = 'Y'
            WHERE PurchaseRequestFlagIsActive = 'Y' 
                AND FIND_IN_SET('TF', PurchaseRequestFlagStatus) 
                AND PurchaseRequestDetailStatus = 'Read' 
                AND PurchaseRequestFlagIsClosed = 'N' 
                AND PurchaseRequestS_RegionalID = ? 
                AND PurchaseRequestM_BranchCode = ? 
                AND NOT EXISTS (
                    SELECT 1 
                    FROM t_goods_transfer 
                    JOIN t_goods_transfer_detail ON T_GoodsTransferDetailIDT_GoodsTransferID = T_GoodsTransferID 
                        AND T_GoodsTransferDetailIsActive = 'Y' 
                    WHERE T_GoodsTransferDetailT_PurchaseRequestFlagID = PurchaseRequestFlagID
                        AND T_GoodsTransferFromS_RegionalID = ? 
                        AND T_GoodsTransferToM_BranchCode = ? 
                )
            ORDER BY PurchaseRequestDate ASC, PurchaseRequestNumber ASC
            LIMIT ? OFFSET ?";

            $quelist = $this->db->query($sqlist, [
                $regionalid, $branchCodeDestin, 
                $regionalid, $branchCodeDestin, 
                $limit, $page
            ]);
            if (!$quelist) {
                $this->sys_error_db("error get listing detail request transfer");
                exit;
            }

            $rows = $quelist->result_array();
            
            foreach ($rows as $key => $value) {
                $itemID         = $value['PurchaseRequestDetailM_ItemID']; 
                $reqUnitID      = $value['RequestItemUnitID'];
                $reqUnitName    = $value['RequestItemUnitName'];

                // Query semua stock yang tersedia di Gudang
                $stockGudang    = $this->getStockForItemRequested($itemID, $regionalid);
                $rows[$key]['StockGudang'] = $stockGudang;

                // Cek dulu apakah salah satu UnitStock ada yang == UnitReq
                $hasDirectMatch = $this->checkStockReqUnitMatch($stockGudang, $reqUnitID);

                $unpackData = $this->checkConversionPossibility($stockGudang, $reqUnitID, $reqUnitName, $itemID);
                $hasUnpackOption = $unpackData['UnpackData']['CanUnpack'] ?? false;

                $rows[$key]['CanDirectUse'] = $hasDirectMatch;
                $rows[$key]['CanUnpack'] = $hasUnpackOption;
                $rows[$key]['UnpackData'] = $unpackData['UnpackData'];
                
                $rows[$key]['ItemRequest'] = [];
            }

            $result = array(
                "records" => $rows,
                "total" => $total
            );

            $this->sys_ok($result);
        } catch (Exception $exc) {
            $message = $exc->getMessage();
            $this->sys_error($message);
        }
    }

    /**
     * Ambil semua stock untuk ItemID di gudang regionalId meskipun beda batch dan UnitItem
     * @param   : int ItemID, int RegionalID
     * @return  : array of objects
     */
    private function getStockForItemRequested($itemID, $regionalID)
    {
        $sqlStock = "SELECT 
            stock.StockID,
            stock.StockItemUnitID,
            itemunit.ItemUnitName as StockItemUnitName,
            stock.StockQty,
            stock.StockBatchNo,
            stock.StockED,
            stock.StockStockNumber,
            stock.StockItemPrice
        FROM warehouse
        JOIN stock ON StockWarehouseID = WarehouseID 
            AND stock.StockQty > 0
        JOIN itemunit ON ItemUnitID = stock.StockItemUnitID 
            AND itemunit.ItemUnitIsActive = 'Y'
        WHERE WarehouseS_RegionalID = ?
            AND WarehouseIsTransit = 'N'
            AND WarehouseType = 'R' 
            AND WarehouseIsActive = 'Y'
            AND stock.StockItemID = ?
        ORDER BY stock.StockED ASC, stock.StockQty DESC";
        
        $qryStock = $this->db->query($sqlStock, [$regionalID, $itemID]);
        
        if (!$qryStock) {
            return [];
        }
        
        return $qryStock->result_array();
    }

    /** 
     * Cek apakah ada Stock memiliki ItemUnit yang sama dengan Unit Request
     * @param   : array($stockGudang), int(reqUnitID)
     * @return  : boolean, true jika ada
    */
    private function checkStockReqUnitMatch($stockGudang, $reqUnitID){
        foreach ($stockGudang as $stock) {
            if ($stock['StockItemUnitID'] == $reqUnitID) {
                return true;
            }
        }
        return false;
    }

    /** 
     * Cek semua kemungkinan Konversi Unit
     * Misal: UnitReq = BUAH, tapi di stock ada LUSIN, dan KODI. Maka akan ada beberapa ConversionOptions
     * @param   : array($stockGudang), int($reqUnitID), string($reqUnitName), int($itemID)
     * @return  : object
     */
    private function checkConversionPossibility($stockGudang, $reqUnitID, $reqUnitName, $itemID) {
        $conversionOptions = [];
        $uniqueCombinations = [];
        
        foreach ($stockGudang as $stock) {
            $stockUnitID = $stock['StockItemUnitID'];
            $stockUnitName = $stock['StockItemUnitName'];
            
            if ($stockUnitID != $reqUnitID) {
                $sqlConv = "SELECT * FROM unitconvert
                    WHERE UnitConvertFromItemUnitID = ?
                    AND UnitConvertToItemUnitID = ?
                    AND UnitConvertIsActive = 'Y' 
                    LIMIT 1";
                $qryConv = $this->db->query($sqlConv, [$stockUnitID, $reqUnitID]);
                
                $conversionKey = $stockUnitID . '_' . $reqUnitID;
                
                // Hanya tambah ke conversionOptions jika FromItemUnitID dan ToItemUnitID berbeda dengan existing
                if ($qryConv && $qryConv->num_rows() > 0 && !in_array($conversionKey, $uniqueCombinations)) {
                    $conversionData = $qryConv->row();
                    $conversionOptions[] = [
                        "FromItemUnitID" => $stockUnitID,
                        "FromItemUnitName" => $stockUnitName,
                        "ToItemUnitID" => $reqUnitID,
                        "ToItemUnitName" => $reqUnitName,
                        "UnitConvertAmount" => $conversionData->UnitConvertAmount
                    ];
                    
                    $uniqueCombinations[] = $conversionKey;
                }
            }
        }
        
        if (empty($conversionOptions)) {
            return [
                'MustUnpack'    => true,
                'UnpackData'    => [
                    "M_ItemID"          => $itemID,
                    "CanUnpack"         => false,
                    "ReasonCannotUnpack"=> "Tidak ditemukan konversi dari unit stock yang tersedia ke unit request {$reqUnitName}"
                ]
            ];
        } else {
            return [
                'MustUnpack' => true,
                'UnpackData' => [
                    "M_ItemID"          => $itemID,
                    "CanUnpack"         => true,
                    "ConversionOptions" => $conversionOptions
                ]
            ];
        }
    }

    public function getDraftDetail()
    {
        try {
            if (!$this->isLogin) {
                $this->sys_error("Invalid Token");
                exit;
            }
            $prm = $this->sys_input;
            $regID = $this->sys_user["S_RegionalID"];
            $goodTfID = $prm['goodTfID'];

            $sql = "SELECT 
                    PurchaseRequestDate,
                    PurchaseRequestNumber,
                    PurchaseRequestDetailM_ItemID,
                    PurchaseRequestDetailItemUnitID,
                    M_ItemDesc,
                    ItemUnitName,
                    COALESCE(SUM(CASE WHEN WarehouseID IS NOT NULL AND StockItemId IS NOT NULL THEN StockQty ELSE 0 END), 0) as StockGudang,
                    PurchaseRequestFlagID,
                    T_GoodsTransferDetailQty as PurchaseRequestFlagQty,
                    T_GoodsTransferDetailQtyTF as PurchaseRequestFlagQtyRest,
                    T_GoodsTransferDetailItemBatchNo,
                    PurchaseRequestFlagStatus,
                    T_GoodsTransferVerifiedBy,
                    T_GoodsTransferVerifiedDate
                FROM t_goods_transfer
                LEFT JOIN t_goods_transfer_detail ON T_GoodsTransferDetailIDT_GoodsTransferID = T_GoodsTransferID
                    AND T_GoodsTransferDetailIsActive = 'Y'
                JOIN purchase_request_flag ON PurchaseRequestFlagID = T_GoodsTransferDetailT_PurchaseRequestFlagID
                    AND PurchaseRequestFlagIsActive = 'Y'
                JOIN purchase_request_detail ON PurchaseRequestDetailID = PurchaseRequestFlagPurchaseRequestDetailID
                    AND PurchaseRequestDetailIsActive = 'Y'
                JOIN purchase_request ON PurchaseRequestID = PurchaseRequestDetailPurchaseRequestID
                    AND PurchaseRequestIsActive = 'Y'
                JOIN m_item ON M_ItemID = PurchaseRequestDetailM_ItemID
                    AND M_ItemItem_CategoryID = 1 
                    AND M_ItemIsActive = 'Y'
                JOIN itemunit ON ItemUnitID = PurchaseRequestDetailItemUnitID
                    AND ItemUnitIsActive = 'Y'
                JOIN m_branch ON M_BranchCode = PurchaseRequestM_BranchCode
                    AND M_BranchIsActive = 'Y'
                JOIN warehouse ON WarehouseS_RegionalID = PurchaseRequestS_RegionalID
                    AND WarehouseIsTransit = 'N'
                    AND WarehouseM_BranchID = 0
                    AND WarehouseIsActive = 'Y'
                LEFT JOIN stock ON StockItemId = M_ItemID
                    AND StockWarehouseID = WarehouseID
                WHERE PurchaseRequestFlagIsActive = 'Y' 
                    AND T_GoodsTransferID = ? 
                    AND T_GoodsTransferIsActive = 'Y'
                GROUP BY PurchaseRequestNumber, PurchaseRequestFlagID";
            $qry = $this->db->query($sql, [$goodTfID]);
            if (!$qry) {
                $this->sys_error_db("error get data draft detail transfer");
                exit;
            }

            $detail = $qry->result_array();

            foreach ($detail as $key => $value) {
                $jsonStr = $value['T_GoodsTransferDetailItemBatchNo'];
                $batches = [];

                if (!empty($jsonStr)) {
                    $decode = json_decode($jsonStr);
                    if ($decode !== null && json_last_error() === JSON_ERROR_NONE) {
                        $batches = $decode;
                    } else {
                        $this->sys_error_db("[Error] decode json for batches number");
                        exit;
                    }
                }
                $detail[$key]['StockGudang'] = [];

                // Foreach batches update StockQty dengan Query Baru
                foreach($batches as &$batch){
                    $sqlCekStock = "SELECT stock.StockID,
                            stock.StockItemUnitID,
                            itemunit.ItemUnitName as StockItemUnitName,
                            stock.StockQty,
                            stock.StockBatchNo,
                            stock.StockED,
                            stock.StockStockNumber,
                            stock.StockItemPrice 
                        FROM stock 
                        JOIN itemunit ON ItemUnitID = stock.StockItemUnitID
                            AND itemunit.ItemUnitIsActive = 'Y'
                        WHERE StockID = ?";
                    $qryCekStock = $this->db->query($sqlCekStock, [$batch->StockID]);
                    if(!$qryCekStock){
                        $this->sys_error_db("[Error] Query StockQty yang terbaru untuk ItemRequest");
                        exit;
                    }

                    $batch->StockQty = $qryCekStock->row()->StockQty;
                    $detail[$key]['StockGudang'][] = $qryCekStock->row();
                }
                unset($batch);
                
                $detail[$key]['ItemRequest'] = $batches;
                unset($detail[$key]['T_GoodsTransferDetailItemBatchNo']);
            }

            $result = array(
                "records" => $detail,
                "total" => sizeof($detail)
            );
            $this->sys_ok($result);
        } catch (Exception $exc) {
            $message = $exc->getMessage();
            $this->sys_error($message);
        }
    }

    public function processTransfer()
    {
        try {
            if (!$this->isLogin) {
                $this->sys_error("Invalid Token");
                exit;
            }

            $userID = $this->sys_user["M_UserID"];
            $data = $this->sys_input;
            $tfdetail = $data['detail'];

            foreach ($tfdetail as $key => $value) {
                $reqFlagID = $data['PurchaseRequestFlagID'];
                $reqTFQty = $data['PurchaseRequestFlagQtyRest'];

                $sqlgetproc = "SELECT SELECT PurchaseRequestFlagQtyProses FROM purchase_request_flag
                    WHERE PurchaseRequestFlagID = ? AND PurchaseRequestFlagIsActive = 'Y'";
                $qrygetproc = $this->db->query($sqlgetproc, [$reqFlagID]);
                if (!$qrygetproc) {
                    $this->sys_error_db("error get current purchase request flag process quantity");
                    exit;
                }
                $qtyProc = $qrygetproc->result_array()[0]['PurchaseRequestFlagQtyProses'];

                $this->db->trans_begin();

                $procQtyUpdt = intval($qtyProc) + intval($reqTFQty);
                $sqlupdtproc = "UPDATE purchase_request_flag 
                    SET 
                        PurchaseRequestFlagQtyProses = ?,
                        PurchaseRequestFlagLastUpdated = NOW(),
                        PurchaseRequestFlagUserID = ? 
                    WHERE PurchaseRequestFlagID = ? 
                        AND PurchaseRequestFlagIsActive = 'Y'";
                $qryupdtproc = $this->db->query($sqlupdtproc, [$procQtyUpdt, $userID, $reqFlagID]);
                if (!$qryupdtproc) {
                    $this->db->trans_rollback();
                    $this->sys_error_db('error update flag update process qty');
                    exit;
                }
            }

            $this->db->trans_commit();
            $this->sys_ok("Berhasil mengupdate Qty process (ID: {$data['GoodsTfID']})");
        } catch (Exception $exc) {
            $message = $exc->getMessage();
            $this->sys_error($message);
        }
    }

    public function getItemBatchListing() {
        try {
            if (!$this->isLogin) {
                $this->sys_error("Invalid Token");
                exit;
            }

            $prm = $this->sys_input;
            $batchNo = "%";
            if ($prm['batchno'] != '') {
                $batchNo = $prm['batchno'] . "%";
            }

            $sqlbatch = "SELECT 
                WarehouseID,
                WarehouseName,
                StockID,
                StockStockNumber,
                StockItemID,
                StockItemUnitID,
                ItemUnitName,
                StockItemPrice,
                StockBatchNo,
                StockED,
                StockQty
            FROM warehouse
            JOIN stock ON StockWarehouseID = WarehouseID 
                AND WarehouseIsActive = 'Y' AND WarehouseIsTransit = 'N'
            JOIN itemunit ON ItemUnitID = StockItemUnitID AND ItemUnitIsActive= 'Y'
            WHERE WarehouseS_RegionalID = ?
                AND WarehouseM_BranchID = 0
                AND StockItemID = ?
                AND StockItemUnitID = ?
                AND StockBatchNo LIKE ?";
            $quebatch = $this->db->query($sqlbatch, [
                $prm['regionalid'],
                $prm['itemid'],
                $prm['unitid'],
                $batchNo
            ]);
            if (!$quebatch) {
                $this->sys_error_db("error get data draft detail transfer");
                exit;
            }
            $batch = $quebatch->result_array();
            $result = array(
                'records' => $batch,
                'total' => sizeof($batch)
            );

            $this->sys_ok($result);
        } catch (Exception $exc) {
            $message = $exc->getMessage();
            $this->sys_error($message);
        }
    }

    public function getUnpackItemBatchListing() {
        try {
            if (!$this->isLogin) {
                $this->sys_error("Invalid Token");
                exit;
            }

            $prm = $this->sys_input;
            $batchNo = "%";
            if ($prm['batchno'] != '') {
                $batchNo = $prm['batchno'] . "%";
            }

            $sqlbatch = "SELECT 
                WarehouseID,
                WarehouseName,
                StockID,
                StockStockNumber,
                StockItemID,
                StockItemUnitID,
                ItemUnitName,
                StockItemPrice,
                StockBatchNo,
                StockED,
                StockQty
            FROM warehouse
            JOIN stock ON StockWarehouseID = WarehouseID 
                AND WarehouseIsActive = 'Y' AND WarehouseIsTransit = 'N'
            JOIN itemunit ON ItemUnitID = StockItemUnitID AND ItemUnitIsActive= 'Y'
            WHERE WarehouseS_RegionalID = ?
                AND WarehouseM_BranchID = 0
                AND StockItemID = ?
                AND StockItemUnitID IN ? -- Bedanya dengan yang getItemBatchListing
                AND StockBatchNo LIKE ?";
            $quebatch = $this->db->query($sqlbatch, [
                $prm['regionalid'],
                $prm['itemid'],
                $prm['unitid'],
                $batchNo
            ]);
            if (!$quebatch) {
                $this->sys_error_db("error get data draft detail transfer");
                exit;
            }
            $batch = $quebatch->result_array();
            $result = array(
                'records' => $batch,
                'total' => sizeof($batch)
            );

            $this->sys_ok($result);
        } catch (Exception $exc) {
            $message = $exc->getMessage();
            $this->sys_error($message);
        }
    }

    public function unpackingStock() {
        try {
            if (!$this->isLogin) {
                $this->sys_error("Invalid Token");
                exit;
            }

            $prm            = $this->sys_input;
            $stockNo        = $prm['StockStockNumber'];
            $stockBatchNo   = $prm['StockBatchNo'];
            $unpackQty      = intval($prm['UnpackQty']); // Qty yang mau di unpack dalam Unit Stock
            $toItemUnitID   = $prm['ToItemUnitID'];
            $convertAmount  = $prm['UnitConvertAmount'];

            
            if($prm['isDebug'] == true){
                $this->db->trans_start(true);
            } else {
                $this->db->trans_start();
            }
            
            /* *== Input Validation ==* */
            // UnitConvertAmount can't 0
            if($convertAmount <= 0){
                throw new Exception("UnitConvertAmount tidak boleh <= 0");
            }

            // Query Stock by StockNumber and BatchNo
            $sqlCekStock    = "SELECT stock.*, 
                ItemUnitName as StockUnitName 
                FROM stock 
                JOIN itemunit
                    ON ItemUnitID = StockItemUnitID
                WHERE StockStockNumber = ? AND StockBatchNo = ? ";
            $qryCekStock    = $this->db->query($sqlCekStock, [$stockNo, $stockBatchNo]);
            
            if(!$qryCekStock || $qryCekStock->num_rows() == 0){
                $error = json_encode($this->db->error(), JSON_PRETTY_PRINT);
                throw new Exception("Error Query DB $error atau Tidak Ditemukan Stock dengan StockNumber {$stockNo} dan Batch $stockBatchNo");
            }

            // Take the StockID
            $rstCek         = $qryCekStock->row();
            $stockID        = $rstCek->StockID; 
            $stockQty       = $rstCek->StockQty;
            $stockUnitName  = $rstCek->StockUnitName;

            // Validate StockQty >= UnpackQty
            if ($stockQty < $unpackQty){
                throw new Exception("Unpack Qty tidak boleh melebih Stock Qty");
            }

            $qryLogs = [];

            // Update StockQty = StockQty - UnpackQty
            $newStockQty    = $stockQty - $unpackQty;
            $sqlUp          = "UPDATE stock SET StockQty = ? WHERE StockID = ?";
            $qryUp          = $this->db->query($sqlUp, [$newStockQty, $stockID]);

            if(!$qryUp){
                throw new Exception("Failed to update StockQty");
            }else{
                $qryLogs["update_qty_unpack"]="Berhasil existing stock dari $stockQty $stockUnitName to {$newStockQty} {$stockUnitName}";

                $sql = "SELECT stock.*, ItemUnitName as StockUnitName 
                    FROM stock 
                    JOIN itemunit ON ItemUnitID = StockItemUnitID
                    WHERE StockID = ?";
                $qry = $this->db->query($sql, [$stockID]);
                $updatedStock = $qry->row();
            }

            // Insert Log, with StockLogStatus = UP_OUT
            $sqlUpLog       = "INSERT INTO stocklog (
                StockLogWarehouseID, StockLogDateTime, StockLogItemID,
                StockLogItemUnitID, StockLogStockNumber, StockLogBatchNo,
                StockLogED, StockLogStatus, StockLogQty, StockLogUserID
            ) VALUES (?,NOW(),?,?,?,?,?,?,?,?)";
            $qryUpLog   = $this->db->query($sqlUpLog, [
                $rstCek->StockWarehouseID,
                $rstCek->StockItemID,
                $rstCek->StockItemUnitID,
                $rstCek->StockStockNumber,
                $rstCek->StockBatchNo,
                $rstCek->StockED,
                'UP_OUT',
                $unpackQty, // OUT sesuai UnpackQty
                $this->sys_user['M_UserID']
            ]);
            
            if(!$qryUpLog){
                throw new Exception("Failed to insert stocklog for UP_OUT");
            }else{
                $qryLogs["insert_log_up_out"] = "Berhasil stocklog UP_OUT {$rstCek->StockQty} {$stockUnitName} for StockNumber {$rstCek->StockStockNumber}";
            }


            $sqlLastSourceCard = "SELECT StockCardAfter FROM stockcard 
                    WHERE StockCardReffID = ? ORDER BY StockCardID DESC LIMIT 1";
            $qryLastSourceCard = $this->db->query($sqlLastSourceCard, [$stockID]);
            if (!$qryLastSourceCard) {
                throw new Exception("Failed to query last stockcard entry for source StockID: {$stockID}");
            }

            $beforeQty = 0;
            if ($qryLastSourceCard->num_rows() > 0) {
                $beforeQty = $qryLastSourceCard->row()->StockCardAfter;
            } else {
                $beforeQty = $stockQty;
            }

            $afterQty = $beforeQty - $unpackQty;

            // Insert Stockcard for source stock (OUT)
            $sqlInsSourceCard = "INSERT INTO stockcard (
                StockCardWarehouseID, StockCardDatetime, StockCardItemID, 
                StockCardItemUnitID, StockCardBatchNo, StockCardED, 
                StockCardReffID, StockCardStatus, 
                StockCardBefore, StockCardIn, StockCardOut, StockCardAfter, 
                StockCardUserID
            ) VALUES (?, NOW(), ?, ?, ?, ?, ?, 'UP_OUT', ?, 0, ?, ?, ?)";

            $qryInsSourceCard = $this->db->query($sqlInsSourceCard, [
                $rstCek->StockWarehouseID, $rstCek->StockItemID, $rstCek->StockItemUnitID, 
                $rstCek->StockBatchNo, $rstCek->StockED, 
                $stockID, $beforeQty, $unpackQty, $afterQty, 
                $this->sys_user['M_UserID']
            ]);

            if (!$qryInsSourceCard) {
                throw new Exception("Failed to insert stockcard for source stock (UP_OUT)");
            }else{
                $qryLogs["insert_card_up_out"] = `Berhasil stockcard UP_OUT pada StockCardID: {$this->db->insert_id()}dengan StockCardReffID: {$stockID}`;
            }

            // Insert new row stock hasil unpack
            $sqlIn  = "INSERT INTO stock (
                StockWarehouseAlmariID, StockWarehouseRackID,
                StockWarehouseID,StockStockNumber,
                StockBatchNo,StockItemID,StockItemUnitID,
                StockED,StockItemPrice,
                StockQty,StockLastUpdated,StockUserID
                ) VALUES (0, 0, ?, fn_numbering('SN'), ?, ?, ?, ?, ?, ?, NOW(), ?)
                ON DUPLICATE KEY UPDATE
                    StockItemPrice = VALUES(StockItemPrice),
                    StockQty = StockQty + VALUES(StockQty),
                    StockLastUpdated = NOW(),
                    StockUserID = VALUES(StockUserID);";

            $qtyConverted = $unpackQty * $convertAmount;
            $priceConverted = $rstCek->StockItemPrice / $convertAmount;

            $qryIn = $this->db->query($sqlIn, [
                $rstCek->StockWarehouseID,
                $rstCek->StockBatchNo,
                $rstCek->StockItemID,
                $toItemUnitID,
                $rstCek->StockED,
                $priceConverted,
                $qtyConverted,
                $this->sys_user['M_UserID']
            ]);

            if(!$qryIn){
                $error = json_encode($this->db->error(), JSON_PRETTY_PRINT);
                throw new Exception("Failed to insert new stock for unpacking. DB Error: $error");
            }

            // Query Stock Hasil Unpack yang Baru untuk data Stocklog dan Stockcard
            $sql = "SELECT stock.*, ItemUnitName as StockUnitName 
                FROM stock 
                JOIN itemunit ON ItemUnitID = StockItemUnitID
                WHERE StockWarehouseID = ? 
                AND StockItemID = ? AND StockItemUnitID = ?
                AND StockBatchNo = ? AND StockED = ?";
            
            $qry = $this->db->query($sql, [
                $rstCek->StockWarehouseID, $rstCek->StockItemID, $toItemUnitID, $rstCek->StockBatchNo, $rstCek->StockED 
            ]);
            
            if(!$qry){
                $err = json_encode($this->db->error(), JSON_PRETTY_PRINT);
                throw new Exception("Error select row stock hasil unpacking");
            }
            $unpackResultStock = $qry->row();
            
            $qryLogs["insert_new_stock"] = "Berhasil insert Stock No. {$unpackResultStock->StockStockNumber} after unpacking Stock No. {$rstCek->StockStockNumber} dari {$unpackQty} {$stockUnitName} ke {$qtyConverted} {$unpackResultStock->StockUnitName}";
            
            // Log hasil konversi  with StockLogStatus = UP_IN
            $sqlUpLog   = "INSERT INTO stocklog (
                StockLogWarehouseID, StockLogDateTime, StockLogItemID,
                StockLogItemUnitID, StockLogStockNumber, StockLogBatchNo,
                StockLogED, StockLogStatus, StockLogQty, StockLogUserID
            ) VALUES (?,NOW(),?,?,?,?,?,?,?,?)";
            $qryUpLog   = $this->db->query($sqlUpLog, [
                $unpackResultStock->StockWarehouseID,
                $unpackResultStock->StockItemID,
                $unpackResultStock->StockItemUnitID,
                $unpackResultStock->StockStockNumber,
                $unpackResultStock->StockBatchNo,
                $unpackResultStock->StockED,
                'UP_IN',
                $qtyConverted, // IN sesuai stock hasil konversi jangan ambil StockQty jaga2 kalau StockQty=StcokQty+convertedQty
                $this->sys_user['M_UserID']
            ]);

            if(!$qryUpLog){
                throw new Exception("Failed to insert stocklog for UP_IN");
            } else{
                $qryLogs["insert_log_up_in"] = "Berhasil stocklog UP_IN {$qtyConverted} {$unpackResultStock->StockUnitName}";
            }

            $sqlLastRstCard = "SELECT StockCardAfter FROM stockcard 
                    WHERE StockCardReffID = ? ORDER BY StockCardID DESC LIMIT 1";
            $qryLastRstCard = $this->db->query($sqlLastRstCard, [$unpackResultStock->StockID]);
            if (!$qryLastRstCard) {
                throw new Exception("Failed to query last stockcard entry for destination stock ID: {$unpackResultStock->StockID}");
            }

            $destBeforeQty = 0;
            if ($qryLastRstCard->num_rows() > 0) {
                $destBeforeQty = $qryLastRstCard->row()->StockCardAfter;
            } else {
                // If no previous stockcard for unpack result stock. Ambil StockQty
                $destBeforeQty = $unpackResultStock->StockQty - $qtyConverted;
            }

            $destAfterQty = $destBeforeQty + $qtyConverted;

            // Insert Stockcard for destination stock (IN)
            $sqlInsDestCard = "INSERT INTO stockcard (
                StockCardWarehouseID, StockCardDatetime, StockCardItemID, 
                StockCardItemUnitID, StockCardBatchNo, StockCardED, 
                StockCardReffID, StockCardStatus, 
                StockCardBefore, StockCardIn, StockCardOut, StockCardAfter, 
                StockCardUserID
            ) VALUES (?, NOW(), ?, ?, ?, ?, ?, 'UP_IN', ?, ?, 0, ?, ?)";

            $qryInsDestCard = $this->db->query($sqlInsDestCard, [
                $unpackResultStock->StockWarehouseID, $unpackResultStock->StockItemID, 
                $unpackResultStock->StockItemUnitID, $unpackResultStock->StockBatchNo, 
                $unpackResultStock->StockED, $unpackResultStock->StockID, 
                $destBeforeQty, $qtyConverted, $destAfterQty, $this->sys_user['M_UserID']
            ]);

            if (!$qryInsDestCard) {
                throw new Exception("Failed to insert stockcard for destination stock (UP_IN)");
            } else{
                $qryLogs["insert_card_up_in"] = `Berhasil stockcard UP_OUT pada StockCardID: {$this->db->insert_id()}dengan StockCardReffID: {$stockID}`;
            }

            $this->db->trans_complete();

            $this->sys_ok([
                "OldStock" => $rstCek,
                "UpdatedStock" => $updatedStock,
                "UnpackResultStock" => $unpackResultStock,
                "TranssactionLogs" => $qryLogs
            ]);

        } catch (Exception $e) {
            $this->db->trans_rollback();
            $message = $e->getMessage();
            $this->sys_error($message);
        }
    }

    function insertUserActivty($id, $type, $userID, $desc = '', $data = '')
    {
        $sql = "INSERT INTO user_activity (
                    UserActivityCode,
                    UserActivityStatus,
                    UserActivityDescription,
                    UserActivityRefID,
                    UserActivityData,
                    UserActivityUserID,
                    UserActivityCreated
                ) VALUES (
                    ?, ?, ?, ?, ?, ?, NOW()
                )";

        // Eksekusi query dengan parameter yang diberikan
        $exec = $this->db->query($sql, [
            'TR',
            $type,
            $desc,
            $id,
            $data,
            $userID
        ]);

        // Jika query gagal, lempar error
        if (!$exec) {
            $this->sys_error_db("Error inserting user activity", $this->db);
            $this->db->trans_rollback();
            exit;
        }
    }

    function toVerifOrApprove($id, $user) {
        $sqlevl = "SELECT * FROM m_user WHERE M_UserIsActive = 'Y' AND M_UserID = ?";
        $quelvl = $this->db->query($sqlevl, [$user['M_UserID']]);
        if (!$quelvl) {
            $this->db->trans_rollback();
            $this->sys_error_db("[Error] get user level");
            exit;
        }

        $level = $quelvl->row_array()['M_UserM_ApproveLevelID'];

        if ($level == '1') {
            $sql = "UPDATE t_goods_transfer SET
                T_GoodsTransferVerifiedBy = ?,
                T_GoodsTransferVerifiedDate = NOW(),
                T_GoodsTransferLastUpdated = NOW()
            WHERE T_GoodsTransferID = ? 
                AND T_GoodsTransferIsActive = 'Y'";

            $que = $this->db->query($sql, [$user['M_UserID'], $id]);
            if (!$que) {
                $this->db->trans_rollback();
                $this->sys_error_db("[Error] update verified by manager");
                exit;
            }
        } elseif ($level == '2') {
            $sql = "UPDATE t_goods_transfer SET
                T_GoodsTransferApprovedBy = ?,
                T_GoodsTransferApprovedDate = NOW(),
                T_GoodsTransferLastUpdated = NOW()
            WHERE T_GoodsTransferID = ? 
                AND T_GoodsTransferIsActive = 'Y'";

            $que = $this->db->query($sql, [$user['M_UserID'], $id]);
            if (!$que) {
                $this->db->trans_rollback();
                $this->sys_error_db("[Error] update approve kareg");
                exit;
            }
        }
    }
}
